深入了解mysql--gap locks,Next-Key Locks


Next-Key Locks

Next-Key Locks是在存儲引擎innodb、事務級別在可重復讀的情況下使用的數據庫鎖,官網上有介紹,Next-Key Locks是行鎖和gap鎖的組合。行鎖是什么我們都很清楚,這篇文章主要簡單分析一下mysql中的gap鎖是什么。innodb默認的鎖就是Next-Key locks。

GAP鎖

gap鎖,又稱為間隙鎖。存在的主要目的就是為了防止在可重復讀的事務級別下,出現幻讀問題。

在可重復讀的事務級別下面,普通的select讀的是快照,不存在幻讀情況,但是如果加上for update的話,讀取是已提交事務數據,gap鎖保證for update情況下,不出現幻讀。

那么gap鎖到底是如何加鎖的呢?

假如是for update級別操作,先看看幾條總結的何時加鎖的規則。

  • 唯一索引
    • 精確等值檢索,Next-Key Locks就退化為記錄鎖,不會加gap鎖
    • 范圍檢索,會鎖住where條件中相應的范圍,范圍中的記錄以及間隙,換言之就是加上記錄鎖和gap 鎖(至於區間是多大稍后討論)。
    • 不走索引檢索,全表間隙加gap鎖、全表記錄加記錄鎖
  • 非唯一索引
    • 精確等值檢索,Next-Key Locks會對間隙加gap鎖(至於區間是多大稍后討論),以及對應檢索到的記錄加記錄鎖。
    • 范圍檢索,會鎖住where條件中相應的范圍,范圍中的記錄以及間隙,換言之就是加上記錄鎖和gap 鎖(至於區間是多大稍后討論)。
  • 非索引檢索,全表間隙gap lock,全表記錄record lock

gap鎖演示例子

假如有以下一張表結構,主鍵簡單點是字符,屬性列只有一個數字,是非唯一索引。

create table gap_table
(
  letter varchar(2) default '' not null primary key,
  num int not null
);

create index gap_table_num_uindex  on gap_table (num);
  
INSERT INTO gap_table (letter, num) VALUES ('d', 3);
INSERT INTO gap_table (letter, num) VALUES ('g', 6);
INSERT INTO gap_table (letter, num) VALUES ('j', 8);

無gap鎖

假如沒有gap鎖,也就是把事務級別調到讀提交,執行以下兩個session

session1 session2
select * from gap_table where num=6 for update,結果是一條  
  INSERT INTO gap_table (letter, num) VALUES (’’, 6);
select * from gap_table where num=6 for update,結果是二條,出現幻讀  

非唯一索引等值檢索gap鎖

假如有gap鎖,演示一個非唯一索引等值檢索gap鎖。也就是把事務級別調到可重復讀,執行以下兩個session

session1 session2
select * from gap_table where num=6 for update,結果是一條。  
  INSERT INTO gap_table (letter, num) VALUES (’’, 6);gap鎖住間隙,阻塞無法插入數據。
select * from gap_table where num=6 for update,結果是一條。不出現幻讀  

唯一索引(主鍵)范圍檢索gap鎖

假如有gap鎖,演示一個唯一索引范圍檢索gap鎖。也就是把事務級別調到可重復讀,執行以下兩個session

session1 session2
select * from gap_table where letter>‘d’ for update,結果是兩條。  
  INSERT INTO gap_table (letter, num) VALUES (‘z’, 10);gap鎖住間隙,阻塞無法插入數據。
select * from gap_table where letter>‘d’ for update,結果是兩條。不出現幻讀  

gap鎖是如何鎖區間?

經過上面的演示可以知道gap鎖的基本作用就是保證可重復讀的情況下不出現幻讀。那么還有一點就是gap是按照什么原則進行鎖的呢?要了解gap鎖的原則,需要先了解innodb中索引樹的結構。下面一張圖片描述了在innodb中,索引的數據結構是如何組織的
索引樹結構

從上面的圖片可以看出,索引結構分為主索引樹和輔助索引樹,輔助索引樹的葉子節點中包含了主鍵數據,主鍵數據影響着葉子節點的排序,gap鎖的關鍵就是鎖住索引樹的葉子節點之間的間隙,不讓新的記錄插入到間隙之中,說起來可能拗口,下面畫圖分析。

非唯一索引gap鎖原則分析

假如還是使用一開始演示的表結構和數據,那么當前的輔助索引樹(數字列)葉子節點的排序結構應該如下。


在這里插入圖片描述
假如執行以下sql的話

INSERT INTO gap_table (letter, num) VALUES ('k', 6);

輔助索引樹的葉子節點結構變為以下圖片結構,k大於g,所以(6,k)排在后面,我們先把(6,k)這條數據刪除,方便后面演示。
在這里插入圖片描述

了解了以上的規則,我們進行實際操作演示gap鎖區間原則,從而推測鎖住哪些區間。

情況1

分別有兩個session,session1執行以下語句:

select * from gap_table where num=6 for update

session2執行以下sql,執行成功:

INSERT INTO gap_table (letter, num) VALUES ('a', 3);

按照排序規則,葉子節點插入結構如下


在這里插入圖片描述

情況2

分別有兩個session,session1執行以下語句:

select * from gap_table where num=6 for update

session2執行以下sql,執行失敗:

INSERT INTO gap_table (letter, num) VALUES ('e', 3);

按照排序規則,葉子節點應該插入如下地方,但是因為區間被鎖插入失敗。


在這里插入圖片描述

情況3

分別有兩個session,session1執行以下語句:

select * from gap_table where num=6 for update

session2執行以下sql,執行失敗:

INSERT INTO gap_table (letter, num) VALUES ('h', 6);

按照排序規則,葉子節點應該插入如下地方,但是因為區間被鎖插入失敗。


在這里插入圖片描述

情況4

分別有兩個session,session1執行以下語句:

select * from gap_table where num=6 for update

session2執行以下sql,執行失敗:

INSERT INTO gap_table (letter, num) VALUES ('h', 7);

按照排序規則,葉子節點應該插入如下地方,但是因為區間被鎖插入失敗。


在這里插入圖片描述

情況5

分別有兩個session,session1執行以下語句:

select * from gap_table where num=6 for update

session2執行以下sql,執行成功:

INSERT INTO gap_table (letter, num) VALUES ('h', 9);

按照排序規則,插入在未鎖區間就能插入成功。


在這里插入圖片描述

總結

當session1執行以下語句:

select * from gap_table where num=6 for update

鎖住的區間如圖所示。按照B+索引樹排序規則,計算好葉子節點插入位置時,在被gap鎖住的區間段內,不能插入任何數據,只有在gap鎖釋放時才能進行插入。


在這里插入圖片描述

在上面的各種情況中鎖住的區間其實是(3,d)到(6,g)和(6,g)到(8,j),落到這個區間段的葉子節點都是無法插入的。主鍵也作為一個信息參與到葉子節點的排序規則中。這里面邊界都是開區間,插入(3,d),(8,j)的數據會報錯主鍵重復而不是lock等待超時。

唯一索引或者非唯一索引范圍檢索gap鎖原則分析

另一種會出現gap鎖的情況就是使用索引時,用到范圍檢索,就會出現gap 鎖。

使用以下表結構。

create table gap_tbz
(
  id   int default 0 not null
    primary key,
  name varchar(11) not null
);

INSERT INTO test.gap_tbz (id, name) VALUES (1, 'a');
INSERT INTO test.gap_tbz (id, name) VALUES (5, 'h');
INSERT INTO test.gap_tbz (id, name) VALUES (8, 'm');
INSERT INTO test.gap_tbz (id, name) VALUES (11, 'ds');

情況1

分別有兩個session,session1執行以下語句:

select * from gap_tbz where id > 5 for update;

session2執行以下sql,執行失敗:

insert into gap_tbz values(6,'cc');

按照排序規則,這里應該是在主鍵索引樹檢索,葉子節點插入結構如下。由於session1執行了范圍的for update sql語句,因此范圍內添加了gap鎖,gap鎖的區間是id在(5,+無限)
在這里插入圖片描述

當執行插入的id范圍在5之前,如下sql,能夠執行成功。

insert into gap_tbz values(4,'cc');

情況2

分別有兩個session,session1執行以下語句:

select * from gap_tbz where id > 5 and id < 11 for update;

session2執行以下sql,執行失敗:

#以下報錯 lock等待超時
insert into gap_tbz values(11,'cc');

#以下報錯 主鍵重復
insert into gap_tbz values(5,'cc');

#從兩種報錯來看也可以看出gap鎖區間是左開右閉

按照排序規則,這里應該是在主鍵索引樹檢索,由於session1執行了范圍的for update sql語句,因此范圍內添加了gap鎖,gap鎖的區間是id在(5,11],唯一索引gap鎖區間是左開右閉。

思考

假如條件是一個非索引列,那么如何處理?

假如是非索引咧,那么將會全表間隙加上gap鎖。

條件是唯一索引等值檢索且記錄不存在的情況,會使用gap lock?

我們要考慮,gap lock是防止幻讀,那么嘗試思考,使用唯一索引所謂條件查找數據for update,如果對應的記錄不存在的話,是無法使用行鎖的。這時候,會使用gap lock來鎖住區間,保證記錄不會插入,防止出現幻讀。

 

文章轉載自:https://blog.csdn.net/tb3039450/article/details/66475638


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM