Skip to content

7. Lock trong InnoDB ​

InnoDB khoá trên entry của index, không khoá “dòng”. Hiểu record lock, gap lock và next-key lock giải thích phần lớn các vụ chờ lock và deadlock khó hiểu.

Các loại lock ​

LockKhoá cái gìSinh ra bởi
Shared (S) / Exclusive (X)S: nhiều bên cùng đọc; X: chỉ một bên, chặn cả S lẫn XFOR SHARE / FOR UPDATE, UPDATE, DELETE
Record lockMột entry của indexTìm theo index unique, hoặc mọi mức isolation
Gap lockKhoảng trống giữa hai entry; chỉ để chặn INSERTREPEATABLE READ trở lên
Next-key lockRecord lock + gap ngay trước nóMặc định khi quét index ở RR
Insert intentionÝ định chèn vào một gap; xung đột với gap lockINSERT
Intention lock (IS/IX)Cờ ở mức bảng báo hiệu “có lock dòng bên trong”Tự động

Thử: gap lock chặn INSERT nào? ​

Bảng users(id PK, age, name) có INDEX(age) và 4 dòng. Chọn câu lệnh mà T1 đang giữ lock, rồi thử để T2 INSERT một dòng mới (id = 100) với age tuỳ chọn.

Isolation
T2 bị chặn, phải chờ T1 commit hoặc rollback (tối đa innodb_lock_wait_timeout = 50 giây). Entry (10, id 100) rơi giữa (10, id 1) và (20, id 2) trên idx_age, khoảng này đang bị gap lock.
idx_age (age, id)−∞(10, id 1)(20, id 2)(30, id 3)(40, id 4)+∞PRIMARY (id)−∞id 1id 2 Anid 3id 4+∞
Ô đỏ là record lock, vạch đỏ là gap lock. Next-key lock = record lock + gap ngay trước nó. Tam giác là vị trí entry mới của T2 sẽ chèn vào.

Index age không unique nên InnoDB khoá next-key (10, 20] và gap (20, 30) để không ai chèn thêm age = 20 (chặn phantom). Thử age = 10: vẫn bị chặn, vì entry (10, id 100) đứng sau (10, id 1) trong index, tức là rơi vào gap đã khoá. Thử age = 30: chèn được, vì (30, 100) đứng sau (30, 3).

  • UPDATE không có index gần như khoá cả bảng: InnoDB khoá mọi entry nó quét qua, không chỉ entry khớp. Ở READ COMMITTED, lock trên dòng không khớp được nhả ngay sau khi kiểm tra.
  • READ COMMITTED không dùng gap lock (trừ khi kiểm tra khoá ngoại và trùng khoá unique): ít chờ lock hơn, đổi lại có thể có phantom. Nhiều hệ thống ghi nhiều chọn RC vì lý do này.

Deadlock ​

Chuyển tiền chéo

T1 chuyển từ tài khoản 1 sang 2, T2 chuyển từ 2 sang 1, cùng lúc.

Bấm “Bước tiếp”.

T1T2
Đồ thị chờ (wait-for graph). Có vòng là deadlock: InnoDB phát hiện ngay, rollback một bên (thường là bên sửa ít dòng hơn) với lỗi 1213.

Mẫu dùng lock hay gặp ​

  • Trừ tồn kho an toàn: UPDATE products SET stock = stock - 1 WHERE id = ? AND stock > 0. Ảnh hưởng 0 dòng nghĩa là hết hàng. Một câu, không cần đọc trước.
  • Optimistic lock: UPDATE … SET …, version = version + 1 WHERE id = ? AND version = ?. Ảnh hưởng 0 dòng nghĩa là có người sửa trước: đọc lại và thử lại. Hợp với xung đột hiếm.
  • Hàng đợi job bằng SKIP LOCKED: SELECT id FROM jobs WHERE status = 0 ORDER BY id LIMIT 10 FOR UPDATE SKIP LOCKED. Nhiều worker lấy job song song, bỏ qua dòng worker khác đang giữ.
  • Chờ lock bao lâu: innodb_lock_wait_timeout mặc định 50 giây, quá lâu với request web. Đặt thấp hơn theo session, hoặc dùng NOWAIT để báo lỗi ngay.

Ghi nhớ

  1. Lock đặt trên entry index; không có index phù hợp thì khoá rất rộng.
  2. RR dùng next-key lock để chặn phantom; RC thì không.
  3. Khoá theo thứ tự nhất quán, transaction ngắn, retry khi gặp lỗi 1213.