上一篇文章主要說明了在可重複讀級別事務級別下,mysql 加鎖的規則以及通過實踐復現了具體的加鎖的操作,但是自認為篇幅過長影響閱讀體驗,只列舉了一些主鍵索引、非主鍵索引的等值以及範圍內的一些簡單場景,這一篇文章讓我們來複現其他有趣的場景,並加以分析。如果沒看過第一篇的可以先看第一篇,最重要的是要動手實踐。如發現文章錯誤,請指出,感激不盡。
先把上篇實踐中的總結規則放上,後面在分析中會用來,這個總結規則來自於實踐,源頭來自於極客時間。還是開頭那一句,本篇的內容都是在可重複讀級別事務下進行。
next-key lock=行鎖+間隙鎖
為什麼先說這個,因為怕你們誤認為 next-key lock
(加鎖的基本單位) 就是間隙鎖,上篇文章其實我也提到過 next-key lock=行鎖+間隙鎖
, 因為上篇文章並沒有特別提到過,所以第一個例子就是為了說明這個。
還是和上篇一樣的表,插入一樣的資料。
現在我們來複現一下這個場景看看發生了什麼
按照上面的圖,我們來複現一下。
這裡,我們來模擬兩個事務,事務 A 啟動的時候,先加了 next-key lock(5,10]
,注意是前開後閉,根據優化2,索引上的等值查詢,向右遍歷直到第一個不符合條件的,這個過程加上 next-key lock(10,15]
,但是因為是等值查詢,並且欄位 d
不是唯一索引,所以 next-key lock(10,15]
退化成間隙鎖即 (10,15)
,綜合下來,事務 A
的 T1
時刻語句加的鎖是 next-key lock(5,10]
和間隙鎖 (10,15)
,事務 B
在 T2
時刻執行編輯操作修改 d=10
這一行,即事務 B
也想在索引 d
上加上 next-key lock(5,10]
鎖,此時進入鎖等待,然後事務 A
在 T3
時刻插入一條語句,被事務 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)
的間隙鎖?
這就要提到間隙鎖和我們說的行鎖是不一樣的。行鎖分為讀鎖和寫鎖。他們之間的衝突可以拿一張圖表示。
也就是說和行鎖有衝突的一定是另一個行鎖。但是間隙鎖不一樣,和間隙鎖有衝突的是往這一個間隙之間插入一條記錄。還記得我上篇文章提到的,間隙鎖是在可重複讀隔離級別下才會生效,它的出現是為了解決幻讀的問題。什麼是幻讀,幻讀指的是在一次事務中前後兩次查詢同一範圍資料的時候,後一次查詢看到了前一次查詢沒有看到的行。這不就是我提到的間隙鎖的衝突嗎。
所以上面的總結就是,間隙鎖本身是不存在衝突的。事務 B
獲取了 (5,10)
的間隙鎖,但是被 d=10
的行鎖鎖住了。事務 A
由於插入的是 (8,8,8)
,又被事務 B
的 (5,10)
間隙鎖鎖住了。導致出現了死鎖,隨後系統回滾了事務 B
,事務 A
得以執行成功。
limit語句
為了演示這個案例,我們需要增加一條資料。下面是現在的資料。
接下來我我們會這樣操作。
很常規的一個操作,按照我們剛才的分析。事務 A
先加 next-key lock(5,10]
,然後加上 (10,15) 的間隙鎖, 所以事務 B
會被鎖住。讓我們來驗證一下。
又被打臉了?,其實我們是知道這條語句加不加 limit
效果是一樣的,因為整個表符合這個條件的就兩條資料,但是加鎖的範圍是不一樣的。limit 2
明確指出了只要兩條,因此 mysql
走到 (c=10,d=30) 就停下來了,也就是說此時的鎖是 next-key lock(5,10]
,此時已滿足 limit 2
,因此並不會給(10,15) 加上間隙鎖,所以事務 B
未被間隙鎖鎖住。這個故事告訴我們,刪除的時候儘量帶上 limmit
引數,不僅不需要刪庫跑路,還可以縮小加鎖的範圍。
鎖等待
我們再來看一個有趣的東西。開始之前我還是把資料還原成開始的六條資料。然後開始兩個事務,執行以下操作。
你可以試著分析,然後再看下面的結果,可能你會驚訝。
事務 A
並不會鎖住 id=10
這條記錄,條件是 >10
, id=10
並不符合條件,所以這條資料不會被鎖住。因此事務 B
在 T2
時刻可以刪除這條記錄,但是事務 B
在 T3
時刻想重新插入這條記錄的時候被鎖住了,場面一度很尷尬。我們可以通過命令來檢視鎖的資訊。
show engine innodb status
主要看下面這些資訊
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) 加以驗證。
可以看到,id=4
的記錄可以正常插入,但是 id=6
會被間隙鎖(5,15) 鎖住。這裡我們得出的總結是,所謂的間隙鎖,完完全全是由間隙右邊的記錄值所決定範圍的。
這篇文章寫到這也告一段落了。剩下對鎖感興趣的可以再列舉其他的一些場景實踐並加以分析。我相信這個過程會很有趣的。
抬頭,看了下手中的勞力士,此時,十二點有餘。想起女朋的貼心提醒,正準備寬衣入睡。猛然驚醒。女友何在?