Skip to content

6. Transaction, isolation và MVCC ​

Nhiều request cùng đọc và ghi một dòng thì mỗi bên thấy gì? InnoDB trả lời bằng MVCC cho đọc thường và lock cho ghi.

ACID ​

Ý nghĩaInnoDB đảm bảo bằng
AtomicityTất cả hoặc không gì cảUndo log lưu giá trị cũ để rollback
ConsistencyRàng buộc (PK, UNIQUE, FK, CHECK) luôn đúng trước và sau transactionPhần lớn do schema và app
IsolationTransaction chạy song song không thấy trạng thái dở của nhauMVCC cho đọc thường, lock cho ghi và locking read
DurabilityCommit xong thì sập máy vẫn cònRedo log được fsync lúc commit (chương 8)

Autocommit mặc định bật: mỗi câu lệnh lẻ là một transaction. Nhiều câu cần đi cùng nhau thì bọc trong BEGIN … COMMIT.

MVCC qua một ví dụ ​

Hai transaction cùng đọc và sửa số dư tài khoản id = 1 (ban đầu 100). Chọn isolation level, chạy từng bước và xem T1 nhìn thấy phiên bản nào trong chuỗi version.

#T1 (trx 201)T2 (trx 202)T1 thấy
1BEGIN; SELECT balance FROM accounts WHERE id = 1;
2BEGIN; UPDATE accounts SET balance = 50 WHERE id = 1;
3SELECT balance FROM accounts WHERE id = 1;
4COMMIT;
5SELECT balance FROM accounts WHERE id = 1;
6SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
7UPDATE accounts SET balance = balance - 10 WHERE id = 1; COMMIT;
Chuỗi version của dòng id = 1 (mới nhất bên trái)
balance = 100trx_id 100đã commit từ trướctrên page
Isolation
REPEATABLE READ
Read view của T1
chưa có

Bấm “Bước tiếp”. Số dư ban đầu là 100.

Cơ chế: mỗi version của dòng ghi lại trx_id của transaction đã tạo ra nó và con trỏ roll_ptr tới version cũ hơn trong undo log. Một read view ghi lại các transaction đang chạy tại thời điểm nó được tạo. Khi đọc, InnoDB đi từ version mới nhất về sau, dừng ở version đầu tiên đã commit trước khi read view ra đời (hoặc do chính transaction đó tạo).

Bẫy lost update ​

Cảnh báo

Ở REPEATABLE READ, bước 5 T1 đọc được 100 nhưng bước 6 (FOR UPDATE) lại thấy 50. Locking read và UPDATE luôn làm việc trên bản mới nhất đã commit, không theo snapshot. Nếu app đọc 100 ở bước 5, tự tính 100 − 10 = 90 rồi SET balance = 90, khoản trừ 50 của T2 biến mất.

Ba cách làm đúng:

  • Để database tính: UPDATE accounts SET balance = balance - 10 WHERE id = 1 AND balance >= 10.
  • Khoá trước khi tính: SELECT balance … FOR UPDATE trong cùng transaction.
  • Optimistic lock bằng cột version (chương 7).

Isolation level trong InnoDB ​

MứcDirty readNon-repeatable readPhantomGhi chú
READ UNCOMMITTEDcócócóĐọc thẳng bản mới nhất, kể cả chưa commit
READ COMMITTEDkhôngcócóMỗi câu SELECT tạo read view mới; không dùng gap lock. Mặc định của PostgreSQL, Oracle
REPEATABLE READkhôngkhônghầu như khôngMặc định InnoDB. Read view tạo ở lần đọc đầu; locking read dùng next-key lock chặn insert vào khoảng
SERIALIZABLEkhôngkhôngkhôngSELECT thường (khi autocommit tắt) thành FOR SHARE, dễ chờ lock và deadlock

Dirty read: đọc dữ liệu chưa commit. Non-repeatable read: đọc lại cùng dòng ra giá trị khác. Phantom: đọc lại cùng điều kiện thấy thêm dòng mới.

Giữ transaction ngắn ​

Transaction mở lâu giữ lock và giữ read view cũ. Purge thread không dọn được undo mà read view đó còn cần (history list length tăng), mọi SELECT phải đi qua chuỗi version dài hơn và hệ thống chậm dần. Không gọi API bên ngoài, không chờ người dùng thao tác khi đang mở transaction.

Ghi nhớ

  1. Mặc định InnoDB là REPEATABLE READ; SELECT thường đọc snapshot, không khoá.
  2. FOR UPDATE / UPDATE đọc bản mới nhất đã commit; đừng tính giá trị mới từ snapshot.
  3. RC tạo read view mỗi câu; RR tạo một lần.
  4. Transaction càng ngắn càng tốt.