專案開發中遇到數據庫死鎖,通過 SHOW INNODB STATUS 發現兩個事務互相等待對方持有的鎖,最終 MySQL 選擇回滾事務 (1)。涉及表 TSK_TASK,結構如下:
(STATUS_ID, MON_TIME) 非聚簇索引-- 事務 1
UPDATE TSK_TASK SET STATUS_ID=1064, UPDATE_TIME=NOW() WHERE STATUS_ID=1061 AND MON_TIME ...
-- 事務 2
UPDATE TSK_TASK SET STATUS_ID=1067, UPDATE_TIME=NOW() WHERE ID IN (9921180)
兩條語句看似操作不同記錄,為何死鎖?關鍵在於 InnoDB 的鎖機制:
UPDATE/DELETE 不僅鎖定 WHERE 掃描的索引記錄,還會鎖定相鄰鍵值(間隙鎖)。KEY_TSKTASK_MONTIME2 索引,鎖定該索引記錄,需回表鎖定主鍵索引(等待中)。STATUS_ID 而鎖定 KEY_TSKTASK_MONTIME2 索引記錄(等待中)。-- 步驟 1:先查符合條件的 ID(僅讀,無鎖衝突)
SELECT ID FROM TSK_TASK WHERE STATUS_ID=1061 AND MON_TIME < DATE_SUB(NOW(), INTERVAL 30 MINUTE);
-- 步驟 2:再按主鍵批量更新(走聚簇索引,避免非聚簇索引鎖定)
UPDATE TSK_TASK SET STATUS_ID=1064 WHERE ID IN (...);
將「掃描非聚簇索引+回表」拆為「先讀後寫」,消除交叉等待,死鎖徹底解決。
SHOW INNODB STATUS 分析死鎖現場。