MySQL 只改一條資料我這麼難的嗎 (二)

Remember發表於2019-12-30

MySQL 只改一條資料我這麼難的嗎(二)
上一篇文章主要說明了在可重複讀級別事務級別下,mysql 加鎖的規則以及通過實踐復現了具體的加鎖的操作,但是自認為篇幅過長影響閱讀體驗,只列舉了一些主鍵索引、非主鍵索引的等值以及範圍內的一些簡單場景,這一篇文章讓我們來複現其他有趣的場景,並加以分析。如果沒看過第一篇的可以先看第一篇,最重要的是要動手實踐。如發現文章錯誤,請指出,感激不盡。

先把上篇實踐中的總結規則放上,後面在分析中會用來,這個總結規則來自於實踐,源頭來自於極客時間。還是開頭那一句,本篇的內容都是在可重複讀級別事務下進行。

MySQL 只改一條資料我這麼難的嗎 (二)

next-key lock=行鎖+間隙鎖

為什麼先說這個,因為怕你們誤認為 next-key lock (加鎖的基本單位) 就是間隙鎖,上篇文章其實我也提到過 next-key lock=行鎖+間隙鎖, 因為上篇文章並沒有特別提到過,所以第一個例子就是為了說明這個。
還是和上篇一樣的表,插入一樣的資料。

MySQL 只改一條資料我這麼難的嗎 (二)

現在我們來複現一下這個場景看看發生了什麼

MySQL 只改一條資料我這麼難的嗎 (二)

按照上面的圖,我們來複現一下。

MySQL 只改一條資料我這麼難的嗎 (二)

這裡,我們來模擬兩個事務,事務 A 啟動的時候,先加了 next-key lock(5,10],注意是前開後閉,根據優化2,索引上的等值查詢,向右遍歷直到第一個不符合條件的,這個過程加上 next-key lock(10,15],但是因為是等值查詢,並且欄位 d 不是唯一索引,所以 next-key lock(10,15] 退化成間隙鎖即 (10,15),綜合下來,事務 AT1 時刻語句加的鎖是 next-key lock(5,10] 和間隙鎖 (10,15),事務 BT2 時刻執行編輯操作修改 d=10 這一行,即事務 B 也想在索引 d 上加上 next-key lock(5,10] 鎖,此時進入鎖等待,然後事務 AT3 時刻插入一條語句,被事務 B 的間隙鎖鎖住,由於互相被鎖,出現了死鎖,InnoDB檢測到死鎖, 讓事務 B 回滾了,然後事務 A 得以插入成功。

這裡的爭議點在於事務 A 的插入為什麼會被事務 B 鎖住,事務 B 執行的時候,在獲取 next-key lock(5,10] 鎖的時候,不是在等待事務 A 釋放鎖嘛?其實整個事務 B 在申請 next-key lock 的時候是有步驟的,這也是為什麼在分析加鎖的過程都是一步步分析的。首先事務 B 先申請的是 (5,10) 的間隙鎖,加鎖成功,當它獲取 d=10 這條行鎖的時候才被鎖住了。可能你會疑惑,為什麼獲取行鎖的時候才被鎖住,A 事務已經鎖住了 (5,10) 的間隙鎖,為什麼事務 B 能獲取到 (5,10) 的間隙鎖?

這就要提到間隙鎖和我們說的行鎖是不一樣的。行鎖分為讀鎖和寫鎖。他們之間的衝突可以拿一張圖表示。

MySQL 只改一條資料我這麼難的嗎 (二)

也就是說和行鎖有衝突的一定是另一個行鎖。但是間隙鎖不一樣,和間隙鎖有衝突的是往這一個間隙之間插入一條記錄。還記得我上篇文章提到的,間隙鎖是在可重複讀隔離級別下才會生效,它的出現是為了解決幻讀的問題。什麼是幻讀,幻讀指的是在一次事務中前後兩次查詢同一範圍資料的時候,後一次查詢看到了前一次查詢沒有看到的行。這不就是我提到的間隙鎖的衝突嗎。

所以上面的總結就是,間隙鎖本身是不存在衝突的。事務 B 獲取了 (5,10) 的間隙鎖,但是被 d=10 的行鎖鎖住了。事務 A 由於插入的是 (8,8,8),又被事務 B(5,10) 間隙鎖鎖住了。導致出現了死鎖,隨後系統回滾了事務 B ,事務 A 得以執行成功。

limit語句

為了演示這個案例,我們需要增加一條資料。下面是現在的資料。

MySQL 只改一條資料我這麼難的嗎 (二)

接下來我我們會這樣操作。
MySQL 只改一條資料我這麼難的嗎 (二)

很常規的一個操作,按照我們剛才的分析。事務 A 先加 next-key lock(5,10],然後加上 (10,15) 的間隙鎖, 所以事務 B 會被鎖住。讓我們來驗證一下。

MySQL 只改一條資料我這麼難的嗎 (二)

又被打臉了?,其實我們是知道這條語句加不加 limit 效果是一樣的,因為整個表符合這個條件的就兩條資料,但是加鎖的範圍是不一樣的。limit 2 明確指出了只要兩條,因此 mysql 走到 (c=10,d=30) 就停下來了,也就是說此時的鎖是 next-key lock(5,10] ,此時已滿足 limit 2,因此並不會給(10,15) 加上間隙鎖,所以事務 B 未被間隙鎖鎖住。這個故事告訴我們,刪除的時候儘量帶上 limmit 引數,不僅不需要刪庫跑路,還可以縮小加鎖的範圍。

鎖等待

我們再來看一個有趣的東西。開始之前我還是把資料還原成開始的六條資料。然後開始兩個事務,執行以下操作。
MySQL 只改一條資料我這麼難的嗎 (二)
你可以試著分析,然後再看下面的結果,可能你會驚訝。

MySQL 只改一條資料我這麼難的嗎 (二)

事務 A 並不會鎖住 id=10 這條記錄,條件是 >10, id=10 並不符合條件,所以這條資料不會被鎖住。因此事務 BT2 時刻可以刪除這條記錄,但是事務 BT3 時刻想重新插入這條記錄的時候被鎖住了,場面一度很尷尬。我們可以通過命令來檢視鎖的資訊。

show engine innodb status

主要看下面這些資訊

MySQL 只改一條資料我這麼難的嗎 (二)

index PRIMARY of table t.t 表示這個語句被鎖住是因為表主鍵上的某個鎖。
lock_mode X locks gap before rec insert intention waiting 可以理解為這個插入動作本身。
gap before rec 表示這是一個間隙鎖而不是記錄鎖。

知道了插入是被間隙鎖鎖住了,但是我們不知道兩點 一.為什麼被鎖住了。二.鎖的範圍是多少。其實它的規則是這樣的,本來在事務 A 的時候只是一個(10,15) 的間隙鎖,但是在事務 B 做了刪除操作以後,此時不存在 id=10 這條記錄,導致間隙鎖向左進行了擴充套件,此時鎖的範圍就是間隙鎖(5,15)。因此,事務 B 的插入語句被鎖住。我們可以試試加入 (4,30,30)和(6,30,30) 加以驗證。
MySQL 只改一條資料我這麼難的嗎 (二)

可以看到,id=4 的記錄可以正常插入,但是 id=6 會被間隙鎖(5,15) 鎖住。這裡我們得出的總結是,所謂的間隙鎖,完完全全是由間隙右邊的記錄值所決定範圍的。

這篇文章寫到這也告一段落了。剩下對鎖感興趣的可以再列舉其他的一些場景實踐並加以分析。我相信這個過程會很有趣的。

抬頭,看了下手中的勞力士,此時,十二點有餘。想起女朋的貼心提醒,正準備寬衣入睡。猛然驚醒。女友何在?

吳親庫裡

相關文章