「死鎖」二字直觀呈現「雙方互相持有對方需要的鎖,陷入無限等待」。但實戰中,很多開發者只知 ERROR 1213,不知其然更不知其所以然。本文結合 InnoDB 行鎖機制,用一個「王二不想讓王一、王三變老」的趣味場景,帶你透過現象看本質,並給出避免死鎖的工程化建議。
-- 事務 A(王二視角:想把王一鎖定 18 歲、王三鎖定 29 歲) START TRANSACTION; UPDATE girl SET age=18 WHERE id=2; -- 鎖住 id=2 UPDATE girl SET age=29 WHERE id=1; -- 試圖鎖 id=1,被事務 B 持有 → 等待 COMMIT; -- 事務 B(時光老人視角:堅持王一 19 歲、王三 30 歲) START TRANSACTION; UPDATE girl SET age=19 WHERE id=1; -- 鎖住 id=1 UPDATE girl SET age=30 WHERE id=2; -- 試圖鎖 id=2,被事務 A 持有 → 等待 COMMIT;
| 時光老人 (事務 B) | 王二 (事務 A) |
|---|---|
START TRANSACTION; | START TRANSACTION; |
UPDATE ... id=1; ✓ 鎖住 id=1 | |
UPDATE ... id=2; ✓ 鎖住 id=2 | |
UPDATE ... id=2; ✗ 等待鎖 | |
UPDATE ... id=1; ✗ 等待鎖 | |
| 循環等待形成 → InnoDB 偵測死鎖 → 回滾其中一個事務(通常是持有較少鎖、權重較低者) | |
[Err] 1213 - Deadlock found when trying to get lock; try restarting transaction
innodb_lock_wait_timeout,直接拋 1213。id=1 → id=2 順序更新,第二個事務只會在第一條 UPDATE id=1 等待行鎖,不會形成循環。-- 統一順序後
-- 事務 A
UPDATE girl SET age=18 WHERE id=1;
UPDATE girl SET age=29 WHERE id=2;
-- 事務 B
UPDATE girl SET age=19 WHERE id=1; -- 等待 A 釋放 id=1
UPDATE girl SET age=30 WHERE id=2; -- 順利拿到 id=2
READ COMMITTED + innodb_locks_unsafe_for_binlog=1(或 5.7+ 默認)可減少間隙鎖,降低死鎖概率,但需評估幻讀風險。SHOW ENGINE INNODB STATUS\G 查看 LATEST DETECTED DEADLOCK 區塊(含雙方 SQL、持有鎖、等待鎖)。innodb_print_all_deadlocks=ON(5.6.2+)將死鎖寫入錯誤日誌,便於事後分析。performance_schema.data_locks、data_lock_waits(8.0+)實時監控鎖等待鏈。死鎖本質是「無序併發」產物。統一訪問順序、縮短事務、選對隔離級別、熱點資源上鎖 四招組合拳,能解決 95% 以上死鎖場景。下次見到 ERROR 1213,別慌——先看 SHOW ENGINE INNODB STATUS,再按圖索驥改順序、加索引、或上分佈式鎖,問題迎刃而解。