mysql間隙鎖實現原理?科學史話2021-03-03 17:48:49

我們都知道Mysql,Oracle PostgreSQL 可以利用MVCC來處理事務,防止加鎖,來提高訪問效率

MVCC只是工作在兩種事務級別底下:(a) Read Committed (b) Repeatable Read;

因為其他兩種:

(c)READ UNCOMMITTED==》總是讀取最新的資料,不符合當前事務版本的資料行,

(d)Serializable則會對所有的行加鎖。

這兩種都不需要MVCC;

參考:Mysql 的InnoDB事務方面的 多版本併發控制如何實現 MVCC

這樣說來 Mysql 也跟其他的資料庫一樣,當 Repeatable Read的時候會出現幻讀的情況,其實不然,Mysql還有一種機制可以保證即使在Repeatable Read級別下面也不會出現幻讀;

這就是間隙鎖:

間隙鎖跟MVCC一起工作。實現事務處理:

Repeatable Read隔離級別: 採用Next-key Lock(間隙鎖) 來解決幻讀問題。因此 Mysql 在Repeatable下面 幻讀,可重複讀,髒讀 三者都不會發生

read committed隔離級別:採用Record鎖,不會出現髒讀,但是會產生“幻讀”問題。 也會出現可重複讀

(我查了很久,這個read committed模式下也會出現可重複讀的問題參考:MySQL中Innodb的事務隔離級別和鎖的關係的講解教程)

間隙鎖簡介:

MySQL InnoDB支援三種行鎖定方式:InnoDB的預設加鎖方式是next-key 鎖。

l 行鎖(Record Lock):鎖直接加在索引記錄上面,鎖住的是key。

l 間隙鎖(Gap Lock):鎖定索引記錄間隙,確保索引記錄的間隙不變。間隙鎖是針對事務隔離級別為可重複讀或以上級別而已的。

l Next-Key Lock :行鎖和間隙鎖組合起來就叫Next-Key Lock。

預設情況下,InnoDB工作在可重複讀(Repeatable Read)隔離級別下,並且會以Next-Key Lock的方式對資料行進行加鎖,這樣可以有效防止幻讀的發生。Next-Key Lock是行鎖和間隙鎖的組合,當InnoDB掃描索引記錄的時候,會首先對索引記錄加上行鎖(Record Lock),再對索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。加上間隙鎖之後,其他事務就不能在這個間隙修改或者插入記錄。 read committed隔離級別下

Gap Lock在InnoDB的唯一作用就是防止其他事務的插入操作,以此防止幻讀的發生。

Innodb自動使用間隙鎖的條件:

(1)必須在Repeatable Read級別下

(2)檢索條件必須有索引(沒有索引的話,mysql會全表掃描,那樣會鎖定整張表所有的記錄,包括不存在的記錄,此時其他事務不能修改不能刪除不能新增)

行鎖(Record Lock)記錄鎖其實很好理解,對錶中的記錄加鎖,叫做記錄鎖,簡稱行鎖。

生活中的間隙鎖(Gap Lock)程式設計的思想源於生活,生活中的例子能幫助我們更好的理解一些程式設計中的思想。生活中排隊的場景,小明,小紅,小花三個人依次站成一排,此時,如何讓新來的小剛不能站在小紅旁邊,這時候只要將小紅和她前面的小明之間的空隙封鎖,將小紅和她後面的小花之間的空隙封鎖,那麼小剛就不能站到小紅的旁邊。這裡的小紅,小明,小花,小剛就是資料庫的一條條記錄。他們之間的空隙也就是間隙,而封鎖他們之間距離的鎖,叫做間隙鎖。

Mysql中的間隙鎖下表中(見圖一),id為主鍵,number欄位上有非唯一索引的二級索引,有什麼方式可以讓該表不能再插入number=5的記錄?

mysql間隙鎖實現原理?

圖一

根據上面生活中的例子,我們自然而然可以想到,只要控制幾個點,number=5之前不能插入記錄,number=5現有的記錄之間不能再插入新的記錄,number=5之後不能插入新的記錄,那麼新的number=5的記錄將不能被插入進來。

那麼,mysql是如何控制number=5之前,之中,之後不能有新的記錄插入呢(防止幻讀)?答案是用間隙鎖,在RR級別下,mysql透過間隙鎖可以實現鎖定number=5之前的間隙,number=5記錄之間的間隙,number=5之後的間隙,從而使的新的記錄無法被插入進來。

間隙是怎麼劃分的?

注:為了方面理解,我們規定(id=A,number=B)代表一條欄位id=A,欄位number=B的記錄,(C,D)代表一個區間,代表C-D這個區間範圍。

圖一中,根據number列,我們可以分為幾個區間:(無窮小,2),(2,4),(4,5),(5,5),(5,11),(11,無窮大)。只要這些區間對應的兩個臨界記錄中間可以插入記錄,就認為區間對應的記錄之間有間隙。例如:區間(2,4)分別對應的臨界記錄是(id=1,number=2),(id=3,number=4),這兩條記錄中間可以插入(id=2,number=3)等記錄,那麼就認為(id=1,number=2)與(id=3,number=4)之間存在間隙。

很多人會問,那記錄(id=6,number=5)與(id=8,number=5)之間有間隙嗎?答案是有的,(id=6,number=5)與(id=8,number=5)之間可以插入記錄(id=7,number=5),因此(id=6,number=5)與(id=8,number=5)之間有間隙的,

間隙鎖鎖定的區域根據檢索條件向左尋找最靠近檢索條件的記錄值A,作為左區間,向右尋找最靠近檢索條件的記錄值B作為右區間,即鎖定的間隙為(A,B)。圖一中,where number=5的話,那麼間隙鎖的區間範圍為(4,11);

間隙鎖的目的是為了防止幻讀,其主要透過兩個方面實現這個目的:(1)防止間隙內有新資料被插入(2)防止已存在的資料,更新成間隙內的資料(例如防止numer=3的記錄透過update變成number=5)

間隙鎖在InnoDB的唯一作用就是防止其它事務的插入操作,以此來達到防止幻讀的發生,所以間隙鎖不分什麼共享鎖與排它鎖。 預設情況下,InnoDB工作在Repeatable Read隔離級別下,並且以Next-Key Lock的方式對資料行進行加鎖,這樣可以有效防止幻讀的發生。Next-Key Lock是行鎖與間隙鎖的組合,當對資料進行條件,範圍檢索時,對其範圍內也許並存在的值進行加鎖!當查詢的索引含有唯一屬性(唯一索引,主鍵索引)時,Innodb儲存引擎會對next-key lock進行最佳化,將其降為record lock,即僅鎖住索引本身,而不是範圍!若是普通輔助索引,則會使用傳統的next-key lock進行範圍鎖定!

要禁止間隙鎖的話,可以把隔離級別降為Read Committed,或者開啟引數innodb_locks_unsafe_for_binlog。

對於快照讀來說,幻讀的解決是依賴mvcc解決。而對於當前讀則依賴於gap-lock解決。

深層次的原理分析: 萊垍頭條

在MVCC併發控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當前讀 (current read)。

快照讀,讀取的是記錄的可見版本 (有可能是歷史版本),不用加鎖。

當前讀,讀取的是記錄的最新版本,並且,當前讀返回的記錄,都會加上鎖,保證其他事務不會再併發修改這條記錄。

在一個支援MVCC併發控制的系統中,哪些讀操作是快照讀?哪些操作又是當前讀呢?以MySQL InnoDB為例:

快照讀:簡單的select操作,屬於快照讀,不加鎖。(當然,也有例外,下面會分析)select * from table where ?; 垍頭條萊

當前讀:特殊的讀操作,插入/更新/刪除操作,屬於當前讀,需要加鎖。select * from table where ? lock in share mode;select * from table where ? for update;insert into table values (…);update table set ? where ?;delete from table where ?;所有以上的語句,都屬於當前讀,讀取記錄的最新版本。並且,讀取之後,還需要保證其他併發事務不能修改當前記錄,對讀取記錄加鎖。其中,除了第一條語句,對讀取記錄加S鎖 (共享鎖)外,其他的操作,都加的是X鎖 (排它鎖)。 萊垍頭條

MySQL/InnoDB定義的4種隔離級別:

Read Uncommited可以讀取未提交記錄。此隔離級別,不會使用,忽略。萊垍頭條

Read Committed (RC)快照讀忽略,本文不考慮。針對當前讀,RC隔離級別保證對讀取到的記錄加鎖 (record lock),存在幻讀現象。萊垍頭條

Repeatable Read (RR)快照讀忽略,本文不考慮。針對當前讀,RR隔離級別保證對讀取到的記錄加鎖 (記錄鎖),同時保證對讀取的範圍加鎖,新的滿足查詢條件的記錄不能夠插入 (間隙鎖),不存在幻讀現象。萊垍頭條

Serializable從MVCC併發控制退化為基於鎖的併發控制。不區別快照讀與當前讀,所有的讀操作均為當前讀,讀加讀鎖 (S鎖),寫加寫鎖 (X鎖)。Serializable隔離級別下,讀寫衝突,因此併發度急劇下降,在MySQL/InnoDB下不建議使用。頭條萊垍