mysql insert 時出現Deadlock死鎖場景分析

案例一:
有一張表A,先更新,如果影響行數爲0,則執行INSERT插入數據。很常見的場景,在生產上也跑了很久,沒有出現什麼問題。但是有一次在測試環境做壓測時居然出現了死鎖,Deadlock found when trying to get lock; try restarting transaction
在這裏插入圖片描述

因爲對mysql鎖不熟悉,爲什麼insert也會死鎖,不是一般在update的時候會死鎖嗎? 很好奇,於是開始尋找原因…

mysql鎖是跟數據庫設置的隔離級別有關係的,不同的隔離級別,鎖也各不相同,只要是爲了解決類似髒讀、幻讀、可重複讀的問題。
select @@tx_isolation; – 查看隔離級別
在這裏插入圖片描述

隔離級別是RR(可重複讀),我們知道,在此隔離級別下,爲了解決幻讀的問題,會有存在間隙鎖和Next-key lock(行鎖 + 間隙鎖),即在update、delete語句後會產生間隙鎖和Next-key lock,如果在併發下,存在兩個事務都事前執行了UPDATE語句(各自持有了gap lock),當INSERT時,要先在插入間隙上獲取插入意向鎖,由於插入數據的間隙存在衝突,所以會互相等待獲取插入意向鎖,即相互競爭,最終會導致一方死鎖。

這也解釋了爲什麼在測試環境出現死鎖。
解決:

  1. 不採用事務,即無事務方式執行,但是如果出現異常則會出現數據不一致的情況。
  2. 調整事務隔離級別爲read commit,RC級別不會產生gap lock

由於我們都知道事務的重要性,所以選擇第二種方式,將隔離級別改爲RC。我們在生產環境一般也就這個級別,所以也解釋了爲什麼在生產沒有出現這個問題。

事務隔離級別調整可以通過spring的聲明式事務方式來實現,通過註解@Transactional

@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
public void calculationEntry(List<CampaignFeedback> campaignFeedbackList, String groovyScript) {
    //todo
}

再次壓測,死鎖問題不再出現。

案例二:
由於業務需要,經常對某張表做更新或者寫入,又不想每次都先查詢是否存在再插入,所以使用了mysql的replace into來實現。
replace into 語法是如果表中存在主鍵或者唯一索引,它會按這個維度去判斷是否存在,如果存在則會先delete在insert,即它會分開兩步來操作,不保證原子性。

開始使用後覺得還是很方便的,畢竟不用自己去多查詢一次,mysql自動幫我們完成了。 但是到了壓測環節,死鎖竟然又出現了。。。。。。

因爲有了前面的經驗,所以這都不是問題,於是按之前的方式改了。在測試環境跑了幾次都沒問題,以爲都是一樣的問題導致,所以就上線了。

上線後,當我去驗證這個功能的時候,尼瑪。。。死鎖問題再一次出現在我的眼前。
在這裏插入圖片描述
我很慌,爲什麼這次不行,難道我代碼被覆蓋了? 翻看git的提交記錄並沒有。。

於是我們去諮詢了DBA,他說replace into 這個不建議使用,因爲在大併發環境下,肯定會出現死鎖。建議我們使用 insert into … values … on duplicate key update … 來替代

於是按DBA建議改了,再次上線後,不管怎麼併發都不會出現死鎖。
原因分析,如果使用replace into mysql會默認開啓Gap lock和Next-key lock,所以即使加了事務配置也不起作用。

最後附上 insert into … on duplicate key update 介紹:
語法:
INSERT INTO tablename (field1,field2, field3, …) VALUES(value1, value2, value3, …) ON DUPLICATE KEY UPDATE field1=value1,field2=value2, field3=value3, …;

也可以使用以下方式(這種支持批量插入)
INSERT INTO TABLE (a,b,c) VALUES
(1,2,3),
(2,5,7),
(3,3,6),
(4,8,2)
ON DUPLICATE KEY UPDATE a=VALUES(a), b=VALUES(b),c=VALUES(c);

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