Mysql 行鎖(記錄鎖、間隙鎖、臨鍵鎖)研究,基於InnoDB

行鎖簡介
通過上一章可以瞭解到,InnoDB下行鎖可細分爲記錄鎖(Record Lock)、間隙鎖(Gap Lock)、臨鍵鎖(Next_Key Lock),是基於索引實現的,其本質上是三種加鎖算法。ps:若不聲明,默認採用RR隔離級別。

注:有些把記錄鎖理解爲行鎖,RL,GL,NKL屬於同一級別,都是排他鎖的一種。

記錄鎖:鎖精確加在某一行上。
間隙鎖:鎖加在不存在的空閒空間,可以是兩個索引記錄之間,也可能是第一個索引記錄之前(負無窮,first-key)或最後一個索引之後(last-key,正無窮)的無限空間。
臨鍵鎖:記錄鎖與間隙鎖組合起來用就叫做Next-Key Lock,就是將鍵及其兩邊的的間隙加鎖(向左掃描掃到第一個比給定參數小的值, 向右掃描掃描到第一個比給定參數大的值, 然後以此爲界,構建一個區間)。
利用Next-Key Lock可以阻止其它事務在鎖定區間內插入數據,因此在一定程度上解決了幻讀問題。
eg:構建表test(id pk,num key)。

id num
3 3
5 5
7 7
10 7
14 9
16 10
20 13

記錄鎖
記錄鎖很好理解,一般要通過主鍵或唯一索引加鎖,就可以較好的實現。

  select * from test where id=6 for update;//該句可以準確且只鎖id=6這一行

間隙鎖
間隙鎖鎖定的時一個開區間,而不是某個鍵,它是基於非唯一索引。需要注意的是用非唯一索引時,要explain下,確保sql走了索引(mysql查詢優化器認爲全表掃描比用索引更快時會鎖表)。那麼什麼情況下出現間隙所呢?
首先:InnoDB存儲引擎,採用RR隔離級別,參數innodb_locks_unsafe_for_binlog=0(靜態參數,默認爲0,表示啓動gap lock,如果設置爲1,表示禁用gap lock,該參數最新版本已被棄用)。
1:唯一索引/主鍵+範圍查詢
鎖住範圍內的已存在的符合要求的行,還會加上範圍內的gap,例子如下:

select * from test where id between 6 and 16 for update;//該句鎖id=7,10,15這三行,
還會鎖id=8,9,11,12,13,14這幾行,  此時另一個事務是插不進去id=8,9,11,12,13,14的數據的。

2:普通索引+絕對範圍(不存在等於的情況)查詢
鎖住範圍內的已存在的符合要求的,還會加上範圍內的gap,例子如下:

事務A:
select * from test where num>10 for update;//該句鎖定範圍(10,正無窮),不包括10。

事務B:
insert into test (id,num)  values(15,10)//成功
 update test set num=18 where id=16//成功 *
  insert into test (id,num)  values(17,10)//失敗*
   insert into test (id,num)  values(17,9)//成功*

3:普通索引+等值查詢
除了鎖定值外,還會加上左右兩側的gap,例子如下:

事務A:
select * from test where num = 7 for update;//該句除了鎖定(5,9)  

事務B:
insert into test (id,num)  values(4,5)//成功
 update test set num=18 where id=5//成功 *
  insert into test (id,num)  values(6,5)//失敗*
   insert into test (id,num)  values(6,20)//成功*
    insert into test (id,num)  values(6,8)//失敗          

對於普通索引需要注意的是,鎖範圍的邊緣值可以更新,但不允許在鎖定範圍內插入與邊緣值相等的行。如例子中加*的sql。

另外,對於2,如果是 select * from test where num>=9;就會鎖定範圍(7,正無窮),而不是[9,正無窮),但是select * from test where id>=16;就會鎖定範圍[16,正無窮),而不是(14,正無窮)。

要想明白這個,首先要了解InoDB的索引,在InnoDB表就是一個索引組織表(IOT),他的主鍵就是它的聚族索引(InnoDB默認對主鍵建立聚簇索引。如果你不指定主鍵,InnoDB會用一個具有唯一且非空值的索引來代替。如果沒有主鍵也沒有合適的唯一索引,那麼innodb內部會生成一個隱藏的主鍵作爲聚集索引,這個隱藏的主鍵是一個6個字節的列,該列的值會隨着數據的插入自增),而普通索引作爲非聚族索引,就會有一個普通索引值對多個主鍵值(如【7,7】,【10,7】),因此mysql對於二級索引和一級索引的間隙鎖處理機制不同的,二級索引(輔助)允許插入相同的值,爲了防止在鎖定範圍前插入邊緣值,所以直接鎖定範圍外的第一個gap。

臨鍵鎖 參考鏈接
在默認情況下,mysql的事務隔離級別是RR,並且innodb_locks_unsafe_for_binlog=0,這時默認採用next-key locks.所謂Next-Key Lock,就是Record lock和gap lock的結合。

分析
下面我們針對大部分的SQL類型分析是如何加鎖的,假設事務隔離級別爲可重複讀。
select … from
不加任何類型的鎖

select…from lock in share mode
在掃描到的任何索引記錄上加共享的(shared)next-key lock,對於主鍵/唯一索引加共享鎖

select…from for update
在掃描到的任何索引記錄上加排它的next-key lock,對於掃描到的主鍵/唯一索引加記錄鎖 ,對於不存在的加間隙鎖

update…where delete from…where
在掃描到的任何索引記錄上加next-key lock,對於掃描到的主鍵/唯一索引加記錄鎖 ,對於不存在的加間隙鎖

insert into…
簡單的insert會在insert的行對應的索引記錄上加一個排它鎖,這是一個record lock,並沒有gap,所以並不會阻塞其他session在gap間隙裏插入記錄。不過若insert操作在間隙鎖範圍內,就會加另一種鎖,官方文檔稱它爲insertion intention gap lock,參考插入意向間隙鎖解析,也就是意向的gap鎖。這個意向gap鎖的作用就是預示着當多事務併發插入相同的gap空隙時,只要插入的記錄不是gap間隙中的相同位置,則無需等待其他session就可完成,這樣就使得insert操作無須加真正的gap lock
這樣的插入機制,會很大程度上提升併發性,如果一個表有一個唯一索引id,表中有記錄1和8,那麼當(1,8)不存在間隙鎖,每個事務都可以在[2,7]之間插入任何記錄,只會對當前插入的記錄加record lock,並不會阻塞其他session插入與自己不同的記錄,因爲他們並沒有任何衝突;當存在間隙鎖,其他session就不能插入任何記錄,直到間隙鎖被釋放,這樣可以防止幻讀。

可以看到T2時會話2對(1,8)加間隙鎖,所以T3會話1會獲得插入意向鎖,被阻塞,而T4時會話2可以正常插入,並對id=6加記錄鎖。

行鎖的兼容矩陣
在這裏插入圖片描述
可以看到,Gap與Gap是互相兼容的 ,請求IIGL與當前Gap是衝突的,參考文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章