MySQL的行級鎖鎖的到底是什么?
往期在文章《介紹Innodb的鎖機制》中提到過關于記錄鎖,但是沒有詳細展開描述。本片文章簡單聊一聊。
數據庫的行級鎖,隨著鎖的細粒度不同,擁有不同的命名。
- 記錄鎖(Record Lock)指的是對索引記錄的鎖定。
- 間隙鎖(Gap Lock)則是對索引記錄之間的間隙進行鎖定。
而Next-Key Lock則是記錄鎖和間隙鎖的融合,同時鎖定索引記錄和間隙。其范圍為左開右閉。
什么是Record Lock
記錄鎖,即Record Lock,是針對索引記錄而言的鎖定。例如,執行以下語句:SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE; 會對滿足條件c1=10的記錄進行鎖定,以防止其他任何事務插入、更新或刪除具有相同c1值的行。
什么是Gap Lock
間隙鎖,即Gap Lock,指的是針對索引記錄之間的間隙,或者是在第一個索引記錄之前或最后一個索引記錄之后的空隙上的鎖定。
在這里,所謂的“間隙”是指InnoDB索引數據結構中可供插入新值的位置。
當你使用SELECT…FOR UPDATE語句鎖定一組行時,InnoDB可以創建鎖,應用于索引中的實際值以及它們之間的間隙。例如,如果你選擇更新所有大于10的值,間隙鎖將阻止另一個事務插入新的大于10的值。
(實際會鎖到+∞,這里為了演示什么是gap簡化了一下)
由于鎖的存在可能影響數據庫的并發性,因此間隙鎖只在Repeatable Reads(可重復讀)這種隔離級別下才會發揮作用。
在Repeatable Reads隔離級別下,針對鎖定的讀操作(例如select ... for update、lock in share mode)、update操作和delete操作,會執行以下加鎖操作:
- 對于具有唯一搜索條件的唯一索引,InnoDB僅鎖定找到的索引記錄,而不鎖定間隙。
- 對于其他搜索條件,InnoDB會鎖定掃描的索引范圍,并使用間隙鎖或next-key鎖來阻止其他事務插入范圍內的間隙。
換句話說,在處理**SELECT FOR UPDATE、LOCK IN SHARE MODE、UPDATE和DELETE**等語句時,除了對具有唯一搜索條件的唯一索引外,還會獲取間隙鎖或next-key鎖,即鎖定其掃描的范圍。
什么是Next-Key Lock
Next-Key鎖是指索引記錄上的記錄鎖和索引記錄之間間隙上的間隙鎖的結合。
假設一個索引包含值10、11、13和20。此索引可能的next-key鎖包括以下區間:
(-∞, 10]
(10, 11]
(11, 13]
(13, 20]
(20, ∞ ]
對于最后一個間隙,∞并不是一個真正的索引記錄,因此,實際上,這個next-key鎖只鎖定最大索引值之后的間隙。
因此,Next-Key鎖的范圍都是左開右閉的。
與Gap Lock一樣,Next-Key Lock只有在InnoDB的可重復讀(RR)隔離級別中才會生效。
談談MySQL加鎖機制
根據丁奇大佬《MySQL實戰45講》中的總結,加鎖規則可以歸納為兩個“原則”、兩個“優化”和一個“bug”:
- 原則 1:加鎖的基本單位是next-key lock,形成一個前開后閉的區間。
- 原則 2:只有查找過程中訪問到的對象才會被加鎖。
- 優化 1:對于索引上的等值查詢,當給唯一索引加鎖時,next-key lock會退化為行鎖。
- 優化 2:對于索引上的等值查詢,在向右遍歷時,且最后一個值不滿足等值條件時,next-key lock會退化為間隙鎖。
- 一個bug:唯一索引上的范圍查詢會一直訪問到不滿足條件的第一個值為止。
當我們執行update t set d=d+1 where id = 7的時候,由于表 t 中沒有 id=7 的記錄,所以:
- 根據原則 1,加鎖單位是 next-key lock,session A 加鎖范圍就是 (5,10];
- 根據優化 2,這是一個等值查詢 (id=7),而 id=10 不滿足查詢條件,next-key lock 退化成間隙鎖,因此最終加鎖的范圍是 (5,10)。
當我們執行select * from t where id>=10 and id<11 for update的時候:
- 根據原則 1,加鎖單位是 next-key lock,會給 (5,10]加上 next-key lock,范圍查找就往后繼續找,找到 id=15 這一行停下來
- 根據優化 1,主鍵 id 上的等值條件,退化成行鎖,只加了 id=10 這一行的行鎖。
- 根據原則 2,訪問到的都要加鎖,因此需要加 next-key lock(10,15]。因此最終加的是行鎖 id=10 和 next-key lock(10,15]。
當執行 select * from t where id>10 and id<=15 for update 時:
- 根據原則 1,加鎖單位是 next-key lock,會給 (10,15] 加上 next-key lock,并且由于 id 是唯一鍵,所以應該循環判斷到 id=15 這一行就停止。
- 但是,InnoDB 實際上會往前掃描到第一個不滿足條件的行,即 id=20。由于這是一個范圍掃描,因此索引 id 上的 (15,20] 這個 next-key lock 也會被鎖上。
假如,數據庫表中當前有以下記錄:
當執行 select id from t where c=5 lock in share mode 時:
- 根據原則 1,加鎖單位是 next-key lock,因此會給 (0,5] 加上 next-key lock。需要注意的是,c 是普通索引,因此不能立即停止于 c=5 這一條記錄,需要向右遍歷,直到找到 c=10 才放棄。
- 根據原則 2,訪問到的都要加鎖,因此要給 (5,10] 加上 next-key lock。
- 根據優化 2:等值判斷,向右遍歷,最后一個值不滿足 c=5 這個等值條件,因此退化成間隙鎖 (5,10)。
- 根據原則 2,只有訪問到的對象才會加鎖。由于這個查詢使用了覆蓋索引,不需要訪問主鍵索引,所以在主鍵索引上沒有加任何鎖。
當執行 select * from t where c>=10 and c<11 for update 時:
- 根據原則 1,加鎖單位是 next-key lock,會給 (5,10] 加上 next-key lock,并繼續向后查找,直到找到 id=15 這一行停止。
- 根據原則 2,訪問到的都要加鎖,因此需要加 next-key lock (10,15]。
- 由于索引 c 是非唯一索引,沒有優化規則,也就是說不會退化為行鎖,因此最終 session A 加的鎖是,索引 c 上的 (5,10] 和 (10,15] 這兩個 next-key lock。
結語
以上,我們介紹了InnoDB中的鎖機制,一共有三種鎖,分別是Record Lock、Gap Lock和Next-Key Lock。
Record Lock表示記錄鎖,鎖的是索引記錄。Gap Lock是間隙鎖,說的是索引記錄之間的間隙。Next-Key Lock是Record Lock和Gap Lock的組合,同時鎖索引記錄和間隙。他的范圍是左開右閉的。
InnoDB的RR級別中,加鎖的基本單位是 next-key lock,只要掃描到的數據都會加鎖。唯一索引上的范圍查詢會訪問到不滿足條件的第一個值為止。
同時,為了提升性能和并發度,也有兩個優化點:
- 索引上的等值查詢,給唯一索引加鎖的時候,next-key lock 退化為行鎖。
- 索引上的等值查詢,向右遍歷時且最后一個值不滿足等值條件的時候,next-key lock 退化為間隙鎖。
關于鎖的介紹,就是這么多了,但是其實,RR的隔離級別引入的這些鎖,雖然一定程度上可解決很多如幻讀這樣的問題,但是也會帶來一些副作用,比如并發度降低、容易導致死鎖等。