1. 程式人生 > 實用技巧 >InnoDB非唯一索引導致死鎖

InnoDB非唯一索引導致死鎖

死鎖日誌

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,減少一次回表
    • 缺點:因為每個事務都增加了查詢和排序,增加了效能損耗,
  • 重試機制

    • 死鎖發生需要一定的巧合,在非唯一索引導致的死鎖問題重試在大多數時候不會有問題
    • 缺點:個別事務會發生失敗,影響使用者體驗
  • 避免長時間持有鎖,減少死鎖概率

    • 避免長事務
    • 優化業務邏輯,在事務儘量接近結束再上鎖,而不是事務剛開始的時候
    • 儘早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