死鎖問題排查過程-間隙鎖的復現以及解決

skyarthur發表於2022-07-10

問題背景

我們在開啟多執行緒對資料庫進行操作的時候,先批量對資料進行刪除,然後再新增,本來想著是考慮到不走更新,效能會提升,但是執行的時候發現報錯,執行的sql等待超時,阻塞了程式,dbcp連線池被打滿,資料庫表訪問不可用。針對這個問題,我們進行了深入的挖掘,逐漸解開了問題的真相。

看下具體的業務實現細節

  • 表定義
    image.png
  • 現在導⼊⼀批資料A的集合,A的定義如下所示:
    image.png

接下來複現問題操作

  • 根據t1的值查詢表a中有沒有對應的記錄
  • 如果有值,則更新t2的值
  • 如果沒查到結果,則執行insert插入操作

這裡批量操作我們採用了多執行緒的方式來執行
image.png
image.png

問題復現

  • step1 - ⾸先插⼊測試資料
    image.png
  • step2 - 我們開啟兩個窗⼝去模擬死鎖。
    Session1:
    image.png
    Session2:
    image.png
    此時,Session 1和Session 2都會對區間(20, ⽆窮⼤)加鎖, 因為間隙鎖只是⽤來防⽌其他事務在區間中插⼊資料。
  • step3 - Session1繼續插⼊操作:
    image.png

此時Session1阻塞(因為Session2持有間隙鎖)。

  • step4- 緊接著Session2繼續插⼊操作:
    image.png

此時Session2死鎖,因為Session1持有間隙鎖。⽽我們的程式碼⾥⾯,因為涉及到多執行緒在事務⾥進⾏先刪除後插⼊的操作,就會發⽣死鎖。

不走更新操作,先刪除,後插入,保證只有2次資料庫操作。

問題原因

查詢相關資料得知,引起死鎖的原因是MYSQL的間隙鎖。
間隙鎖

間隙鎖(Gap Lock)是Innodb在可重複讀提交下為了解決幻讀問題時引⼊的鎖機制,幻讀的問題存在是因為新增或者更新操作,這時如果進⾏範圍查詢的時候(加鎖查詢),會出現不⼀致的問題,這時使⽤不同的⾏鎖已經沒有辦法滿⾜要求,需要對⼀定範圍內的資料進⾏加鎖,間隙鎖就是解決這類問題的。在可重複讀隔離級別下,資料庫是通過⾏鎖和間隙鎖共同組成的(next-key lock)來實現的。
⾏鎖和間隙鎖的定義如下所示:

  • record lock:⾏鎖,也就是僅僅鎖著單獨的⼀⾏。
  • gap lock:間隙鎖,僅僅鎖住⼀個區間(注意這⾥的區間都是開區間,也就是不包括邊界值)。
  • next-key lock:record lock+gap lock,所以next-key lock也就半開半閉區間,且是下界開,上界閉。
    加鎖規則特性

加鎖規則有⼀些特性,其中我們需要關注的有:

  • 加鎖的基本單位是(next-key lock),他是前開後閉原則
  • 查詢過程中訪問的物件會增加鎖
  • 間隙鎖僅阻⽌其他事務插⼊間隙。在刪除資料的時候,會去加間隙鎖,但是多個事務是可以同時對⼀個間隙去加鎖的,⽽如果需要對該間隙進⾏插⼊,則需要等待鎖釋放。

解決方式

1、將事務隔離級別將為read commit.

間隙鎖只存在於可重複讀的隔離級別下,因為要防⽌幻讀。這個⽅法不現實,不可能為了這個問題 把整個線上資料庫隔離級別給改掉。
2、避免先刪除後插⼊的操作.

修改程式碼,避免先刪除後插⼊的操作。犧牲效能,在業務中,先根據唯⼀索引查出存在的記錄,然後對存在的記錄進⾏根據主鍵Id在迴圈中更新,對於不存在的記錄進⾏批量插⼊。

相關文章