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
