InnoDB非唯一索引導致死鎖
阿新 • • 發佈:2020-10-14
死鎖日誌
SHOW ENGINE INNODB STATUS; 獲取最近發生的deadlock
配置:innodb_print_all_deadlocks並在error log檢視
翻譯
- 行號:“1: len 8; hex 000000000000B75; asc”:B75(16進位制) = 2933(10進位制)。
- (1)WAITING FOR THIS LOCK TO BE GRANTED:事務(1)等待獲取鎖
- (2)HOLDS THIS LOCK(S):事務(2)持有該鎖
過程
- TRANSACTION(1)通過update語句1獲取2934行記錄鎖,等待2933行記錄鎖釋放;
- TRANSACTION(2)持有2933行記錄鎖,等待2934行記錄鎖釋放。
- MYSQL發現死鎖:WE ROLL BACK TRANSACTION(1)。
分析
表T結構:
- mNo:非唯一索引
- id:主鍵
簡化語句:
- select * from T where mNo = 123:mNo為非唯一索引,分別返回id = 2933和2934行兩條記錄
- update語句1(id = 2933):update T set flag = 0 where mNo = 123 and f = 1;
- update語句2(id = 2934):update T set flag = 0 where mNo = 123 and f = 2;
- explain update T set flag = 0 where mNo = 123 and f = 1;
- possible_keys:mNo_index(計劃用到的索引)
- rows:2(計劃查詢的行數)
即使只查詢f=1的記錄,仍會查詢2行
由於MySql是在索引上行鎖,兩個事務同時用一個key–mNo_index索引,兩個事務都需要同時對mNo=123的兩條記錄上行鎖,當兩個記錄上鎖順序不一樣(事務1鎖2933行,事務2鎖2934行)就有機率發生死鎖。
解決方案
-
固定上鎖順序
- 先select所有的行,然後按照主鍵id排序,每個事務都按順序上鎖。
- 每個事務都先上鎖2933行,如果沒搶到2933行就阻塞等待,不會去搶2934行
- 優化點:僅select主鍵可以在當前索引樹直接拿到主鍵id,減少一次回表
- 缺點:因為每個事務都增加了查詢和排序,增加了效能損耗,
- 先select所有的行,然後按照主鍵id排序,每個事務都按順序上鎖。
-
重試機制
- 死鎖發生需要一定的巧合,在非唯一索引導致的死鎖問題重試在大多數時候不會有問題
- 缺點:個別事務會發生失敗,影響使用者體驗
-
避免長時間持有鎖,減少死鎖概率
- 避免長事務
- 優化業務邏輯,在事務儘量接近結束再上鎖,而不是事務剛開始的時候
- 儘早commit
具體需要根據業務量和死鎖發生的概率權衡用哪種方案
InnoDB如何發現死鎖
配置:innodb_deadlock_detect(預設開)
事務等待圖wait-for-graph(有向圖)
一旦有向圖形成了環,表示造成死鎖,InnoDB報錯死鎖並回滾相應事務
References
How to Minimize and Handle Deadlocks:https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html