Mysql加鎖過程詳解(9)-innodb下的記錄鎖,間隙鎖,next-key鎖

crazyYong發表於2017-11-12

 

你需要知道的
之前我們介紹了排他鎖,其實innodb下的記錄鎖(也叫行鎖),間隙鎖,next-key鎖統統屬於排他鎖。

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

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

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


圖一

根據上面生活中的例子,我們自然而然可以想到,只要控制幾個點,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自動使用間隙鎖的條件:
(1)必須在RR級別下
(2)檢索條件必須有索引(沒有索引的話,mysql會全表掃描,那樣會鎖定整張表所有的記錄,包括不存在的記錄,此時其他事務不能修改不能刪除不能新增)

接下來,通過實際操作觀察下間隙鎖的作用範圍


圖三 表結構


案例一:

````
session 1:
start  transaction ;
select  * from news where number=4 for update ;

session 2:
start  transaction ;
insert into news value(2,4);#(阻塞)
insert into news value(2,2);#(阻塞)
insert into news value(4,4);#(阻塞)
insert into news value(4,5);#(阻塞)
insert into news value(7,5);#(執行成功)
insert into news value(9,5);#(執行成功)
insert into news value(11,5);#(執行成功)
````

 

 

檢索條件number=4,向左取得最靠近的值2作為左區間,向右取得最靠近的5作為右區間,因此,session 1的間隙鎖的範圍(2,4),(4,5),如下圖所示:



間隙鎖鎖定的區間為(2,4),(4,5),即記錄(id=1,number=2)和記錄(id=3,number=4)之間間隙會被鎖定,記錄(id=3,number=4)和記錄(id=6,number=5)之間間隙被鎖定。

因此記錄(id=2,number=4),(id=2,number=2),(id=4,number=4),(id=4,number=5)正好處在(id=3,number=4)和(id=6,number=5)之間,所以插入不了,需要等待鎖的釋放,而記錄(id=7,number=5),(id=9,number=5),(id=11,number=5)不在上述鎖定的範圍內,因此都會插入成功。


案例二:

````
session 1:
start  transaction ;
select  * from news where number=13 for update ;

session 2:
start  transaction ;
insert into news value(11,5);#(執行成功)
insert into news value(12,11);#(執行成功)
insert into news value(14,11);#(阻塞)
insert into news value(15,12);#(阻塞)
update news set id=14 where number=11;#(阻塞)
update news set id=11 where number=11;#(執行成功)
````

 


檢索條件number=13,向左取得最靠近的值11作為左區間,向右由於沒有記錄因此取得無窮大作為右區間,因此,session 1的間隙鎖的範圍(11,無窮大),如下圖所示:


此表中沒有number=13的記錄的,innodb依然會為該記錄左右兩側加間隙鎖,間隙鎖的範圍(11,無窮大)。

有人會問,為啥update news set id=14 where number=11會阻塞,但是update news set id=11 where number=11卻執行成功呢?

間隙鎖採用在指定記錄的前面和後面以及中間的間隙上加間隙鎖的方式避免資料被插入,此圖間隙鎖鎖定的區域是(11,無窮大),也就是記錄(id=13,number=11)之後不能再插入記錄,update news set id=14 where number=11這條語句如果執行的話,將會被插入到(id=13,number=11)的後面,也就是在區間(11,無窮大)之間,由於該區間被間隙鎖鎖定,所以只能阻塞等待,而update news set id=11 where number=11執行後是會被插入到(id=13,number=11)的記錄前面,也就不在(11,無窮大)的範圍內,所以無需等待,執行成功。


案例三:

````
session 1:
start  transaction ;
select  * from news where number=5 for update;

session 2:
start  transaction ;
insert into news value(4,4);#(阻塞)
insert into news value(4,5);#(阻塞)
insert into news value(5,5);#(阻塞)
insert into news value(7,11);#(阻塞)
insert into news value(9,12);#(執行成功)
insert into news value(12,11);#(阻塞)
update news set number=5 where id=1;#(阻塞)
update news set id=11 where number=11;#(阻塞)
update news set id=2 where number=4 ;#(執行成功)
update news set id=4 where number=4 ;#(阻塞)
````

 

 

檢索條件number=5,向左取得最靠近的值4作為左區間,向右取得11為右區間,因此,session 1的間隙鎖的範圍(4,5),(5,11),如下圖所示:



有人會問,為啥insert into news value(9,12)會執行成功?間隙鎖採用在指定記錄的前面和後面以及中間的間隙上加間隙鎖的方式避免資料被插入,(id=9,number=12)很明顯在記錄(13,11)的後面,因此不再鎖定的間隙範圍內。

為啥update news set number=5 where id=1會阻塞?
number=5的記錄的前面,後面包括中間都被封鎖了,你這個update news set number=5 where id=1根本沒法執行,因為innodb已經把你可以存放的位置都鎖定了,因為只能等待。

同理,update news set id=11 where number=11由於記錄(id=10,number=5)與記錄(id=13,number=11)中間的間隙被封鎖了,你這句sql也沒法執行,必須等待,因為存放的位置被封鎖了。


案例四:

session 1:
start  transaction;
select * from news where number>4 for update;

session 2:
start  transaction;
update news set id=2 where number=4 ;#(執行成功)
update news set id=4 where number=4 ;#(阻塞)
update news set id=5 where number=5 ;#(阻塞)
insert into news value(2,3);#(執行成功)
insert into news value(null,13);#(阻塞)

 

 

檢索條件number>4,向左取得最靠近的值4作為左區間,向右取無窮大,因此,session 1的間隙鎖的範圍(4,無窮大),如下圖所示:


session2中之所以有些阻塞,有些執行成功,其實就是因為插入的區域被鎖定,從而阻塞。


next-key鎖
next-key鎖其實包含了記錄鎖和間隙鎖,即鎖定一個範圍,並且鎖定記錄本身,InnoDB預設加鎖方式是next-key 鎖。
上面的案例一session 1中的sql是:select * from news where number=4 for update ;
next-key鎖鎖定的範圍為間隙鎖+記錄鎖,即區間(2,4),(4,5)加間隙鎖,同時number=4的記錄加記錄鎖。



from:http://www.jianshu.com/p/bf862c37c4c9

 

相關文章