喜大普奔,微信給我的公衆號開了留言功能!!!有緣看到這篇文章的朋友,可以留個言互動下,謝謝~
最近線上偶發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_displaydataid
的MX4TYZIKTKSZCAABAAAAAAY8$f$w_4
位置上WAITING FOR THIS LOCK TO BE GRANTED
: 該事務在等待索引idx_displaydataid
的MX4TYXYKTJ6VKAABAAAAADY8$m$462
位置上,等待一個X鎖
2)事務2
HOLDS THE LOCK(S)
: 該事務持有兩個S鎖,其中一個鎖在索引idx_displaydataid
的MX4TYXYKTJ6VKAABAAAAADY8$m$462
位置上WAITING FOR THIS LOCK TO BE GRANTED
: 該事務在等待索引idx_displaydataid
的MX4TYZIKTKSZCAABAAAAAAY8$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源創計劃”,歡迎正在閱讀的你也加入,一起分享。