Giao diện
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
| Lock | Khoá 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 X | FOR SHARE / FOR UPDATE, UPDATE, DELETE |
| Record lock | Một entry của index | Tìm theo index unique, hoặc mọi mức isolation |
| Gap lock | Khoảng trống giữa hai entry; chỉ để chặn INSERT | REPEATABLE READ trở lên |
| Next-key lock | Record 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 lock | INSERT |
| 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.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”.
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_timeoutmặc định 50 giây, quá lâu với request web. Đặt thấp hơn theo session, hoặc dùngNOWAITđể báo lỗi ngay.
Ghi nhớ
- Lock đặt trên entry index; không có index phù hợp thì khoá rất rộng.
- RR dùng next-key lock để chặn phantom; RC thì không.
- Khoá theo thứ tự nhất quán, transaction ngắn, retry khi gặp lỗi 1213.