MySQL死鎖排查,原來我一直沒懂。。。


喜大普奔,微信給我的公衆號開了留言功能!!!有緣看到這篇文章的朋友,可以留個言互動下,謝謝~

最近線上偶發MySQL的死鎖異常,發現原來很多理論都只背了個結論,細節都是魔鬼。

比如,MySQL在RR級別用gap lock防止幻讀,RC級別就沒有gap lock嗎?

不妨來一起看看,MySQL的死鎖問題有哪些你不瞭解的細節。

1、死鎖信息

1.1 數據庫基本信息

  • 版本:MySQL 5.7

  • 隔離級別:  READ-COMMITTED

  • 表結構:

1.2 死鎖日誌



死鎖日誌分析
1)事務1

  • HOLDS THE LOCK(S) : 該事務持有兩個S鎖,其中一個鎖在索引idx_displaydataidMX4TYZIKTKSZCAABAAAAAAY8$f$w_4 位置上

  • WAITING FOR THIS LOCK TO BE GRANTED : 該事務在等待索引idx_displaydataidMX4TYXYKTJ6VKAABAAAAADY8$m$462位置上,等待一個X鎖

2)事務2

  • HOLDS THE LOCK(S) : 該事務持有兩個S鎖,其中一個鎖在索引idx_displaydataidMX4TYXYKTJ6VKAABAAAAADY8$m$462 位置上

  • WAITING FOR THIS LOCK TO BE GRANTED : 該事務在等待索引idx_displaydataidMX4TYZIKTKSZCAABAAAAAAY8$f$w_4一個X鎖

死鎖原因看起來比較清楚,鎖互斥且循環等待,造成了死鎖。

1.3 死鎖疑點

隨着我仔細分析上面的日誌,發現又不是那麼簡單,或者說有幾個疑點困惑:

  • Question1: gap before rec 表示一個間隙鎖,我們數據庫的隔離級別是 RC,怎麼還有間隙鎖?

  • Question2: gap before rec insert intention好像叫插入意向鎖,到底是個啥?

  • Question3: INSERT語句,到底有幾把鎖?爲什麼會獲得S鎖?

2、死鎖答疑

2.1 爲什麼RC級別下還有間隙鎖?

網上很多博客視頻都會說RC級別下間隙鎖會失效,然後搬出官方文檔的原話:

Gap locking can be disabled explicitly. This occurs if you change the transaction isolation level to READ COMMITTED or enable the innodb_locks_unsafe_for_binlog system variable (which is now deprecated).

但是,官方文檔後面還有一句:

In this case, gap locking is disabled for searches and index scans and is used only for foreign-key constraint checking and duplicate-key checking.

意思是RC級別下間隙鎖會用於外鍵和唯一鍵檢查。

2.2 插入意向鎖到底是什麼?

查閱了官方文檔,我們可以瞭解到,插入意向鎖(Insert Intention Locks
)其實是一種特殊的gap lock,在行插入前,要獲取這個鎖(所以這個鎖是在行排它鎖之前獲取)。

假設存在值爲 4 和 7 的索引記錄,嘗試插入值 5 和 6 的兩個事務,在獲取插入行上的排它鎖之前,使用插入意向鎖鎖定間隙,即在(4,7)上加 gap lock

但是這兩個事務不會互相沖突等待

但是如果這個區間已經存在其他普通 gap lock(比如其他事務用select for update 或者 select in share mode獲取了gap lock),則插入意向鎖會被阻塞。

注意,這也是我們常說的gap lock能夠避免幻讀的原因,可以阻止INSERT獲取插入意向鎖

如果多個事務插入相同數據導致唯一衝突,則在重複的索引記錄上加讀鎖,這個我們後面再詳細介紹。

簡單來說,插入意向鎖的屬性爲:

  • 它不會阻塞其他任何鎖;

  • 它本身僅會被gap lock阻塞

2.3 INSERT到底有幾把鎖

1)普通INSERT

  • 先加插入意向鎖,插入意向鎖之間不衝突。比如4,7兩行之間,可以同時插入5、6兩行。

  • 插入成功後,加對應行鎖。

2)INSERT唯一索引衝突

INSERT的時候,發現唯一索引衝突,觸發duplicate-key error 後,會先獲取到一個next-key讀鎖。

session A第二次插入時,發生唯一鍵衝突的時候,並不只是簡單地報錯返回,還在衝突的索引上加了鎖。

一個 next-key lock 就是由它右邊界的值定義的。這時候,session A 持有索引 c 上的 (5,10]共享 next-key 讀鎖,所以session B插入時也被阻塞了。

總結一下:

  • 通常INSERT語句,先加插入意向鎖,插入成功後,獲得行鎖,排它鎖

  • 在INSERT之前,先通過插入意向鎖,判斷是否可以插入(僅會被gap lock阻塞)

  • 當插入唯一衝突時,在重複索引上添加next-key讀鎖

事務1 插入成功未提交,獲取了排它鎖,但是事務1最終可能會回滾,所以其他重複插入事務不應該直接失敗,這個時候他們改爲申請讀鎖。

3、總結下INSERT幾種經典死鎖

3.1 模式一:唯一索引併發寫入回滾

  • session A插入,獲得行寫鎖;

  • session B、C插入時,發現唯一索引衝突,同時請求next-key讀鎖,鎖排隊;

  • session A回滾,釋放行寫鎖,session B、C同時獲得next-key讀鎖

  • session B、C嘗試插入,需要獲取插入意向鎖,互斥等待,觸發死鎖

3.2 模式二:唯一索引併發刪除插入

  • session A 拿到行寫鎖(delete from where 正常情況是獲取next-key鎖,只有當唯一索引命中時會變成行鎖)

  • sessionB/C發現唯一索引衝突,觸發duplicate-key error 後,同時請求next-key讀鎖,鎖排隊;

  • session A commit後,刪除成功,釋放行寫鎖,sessionB/C 獲得next-key讀鎖

  • session B、C 嘗試插入,需要獲取插入意向鎖,互斥等待,觸發死鎖    

3.3 模式三:唯一索引併發刪除後插入

  • session A的delete from 拿到行寫鎖

  • session B的delete from 希望獲取行寫鎖,等待

  • session A的insert 唯一索引衝突,希望獲取next-key讀鎖,鎖排隊,並且排在B的後面,形成死鎖

4、總結下加鎖原則

這裏還有一個加鎖原則比較重要,一個SQL到底要加哪些鎖。

查閱了網上一些資料,做了一個總結,具體案例就不展開了:

  • MySQL的鎖是加在索引上的

  • 查詢過程中訪問到的索引對象纔會加鎖(沒有索引就可能鎖全表)

  • 加鎖的基本單位是next-key lock(前開後閉)

  • 等值查詢上MySQL的優化:索引上的等值查詢,如果是唯一索引,next-key lock會退化爲行鎖,如果不是唯一索引,需要訪問到第一個不滿足條件的值,此時next-key lock會退化爲間隙鎖

  • 範圍查詢:無論是否是唯一索引,範圍查詢都需要訪問到不滿足條件的第一個值爲止

5、死鎖優化建議

  • 避免大事務,儘量拆小

  • 避免 經典死鎖模式

  • 批量操作儘量排序後,按相同順序插入或者刪除

  • 儘量使用普通索引而不是唯一索引,即使使用唯一索引,也應該儘量避免重複插入

  • 可以考慮使用RC隔離級別加binlog_format=row模式,而不是RR隔離級別







往期熱門筆記合集推薦:


原創:阿丸筆記(微信公衆號:aone_note),歡迎 分享,轉載請保留出處。

沒有留言功能的悲傷,掃描下方二維碼「加我」聊些有的沒的吧~

                                                                              覺得不錯,就點個  再看 吧👇



本文分享自微信公衆號 - 阿丸筆記(aone_note)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章