面試官:什么是 Next-Key Lock?作用是什么?
Next-Key Lock 是 InnoDB 存儲引擎中非常基礎也是非常重要的一個鎖。今天來聊一聊 Next-Key Lock。
首先我們回顧一下 MySQL 的事務隔離級別。
- 串行化(Serializable):事務對數據讀寫都是串行的。
- 可重復讀(Repeatable Read):事務執行過程中,多次讀取同一行數據,讀取結果一致。MySQL 默認隔離級別就是可重復讀。
- 讀已提交數據(Read Committed):事務執行過程中,如果有其他事務修改了數據并且提交事務,當前事務可以讀取到最新提交的數據。
- 讀未提交數據(Read Uncommitted):事務執行過程中,可以讀取到其他事務未提交的數據。
可重復讀隔離級別解決了幻讀問題,而解決的方式就是通過 MVCC 和 Next-Key Lock。
幻讀:同一事務內多次查詢同一范圍內的數據,因其他事務插入或刪除符合條件的數據,導致事務在后面讀取到的結果集不一樣,像產生了幻覺。
介紹
Next-Key Lock 是間隙鎖(gap lock)和行鎖(index-record lock)的組合。
- 行鎖: 鎖定索引中的某一條具體記錄。
- 間隙鎖: 鎖定索引記錄之間的間隙,它鎖定的是一個范圍,這個范圍內不存在數據。例如,如果鎖定了 (10, 20) 這個范圍,那其他事務就不能在這個范圍內插入新記錄。
一個 Next-Key Lock,也就是行鎖加上該行之前的間隙鎖,因此,它鎖定的是一個前開后閉區間。
我們假設有一張表 t,包含(id,a,b)3 個字段,其中 id 是主鍵,字段 a 上面有普通索引,字段 b 沒有索引。建表語句如下:
CREATE TABLE `t` (
`id` int(10) NOT NULL,
`a` int(10) DEFAULT NULL,
`b` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_a` (`a`)) ENGINE=InnoDB;
表中有 4 條記錄,分別(5,5,5),(10,10,10),(15,15,15),(20,20,20),那如果執行下面語句:
select * from t where id = 7 for update;
這樣會對 id= 10 的記錄加 Next-Key Lock,鎖定的是 (5, 10] 這個區間,也就是說,除了鎖住 id = 10 這條記錄(行鎖),同時間隙鎖鎖住了 5 和 10 之間的間隙(5,10)。
而如果執行下面這條 SQL,則會對整張表加 Next-Key Lock,因為字段 c 沒有索引,新插入的數據都可能包含 c = 6。加鎖后,形成 5 個范圍:
select * from t where c = 6 for update;
圖片
加上了 Next-Key Lock,可以阻止其他事務在被鎖定的間隙中插入新的記錄,從而保證了當前事務中多次執行相同查詢時,不會有新的記錄查出來,解決了幻讀問題。
在可重復隔離級別下,執行下面幾個 SQL 時,都可能會對掃描過程中訪問到的記錄加 Next-Key Lock:
SELECT * from t where id =xxx FOR UPDATE;//id 不存在
SELECT * from t where a =xxx FOR UPDATE;
SELECT * from t where b =xxx FOR UPDATE;
SELECT * from t where id BETWEEN 10 AND 20;
SELECT * from t where id =xxx LOCK IN SHAREMODE;
UPDATE t set b = xxx where id = yyy;
DELETE from t where id = yyy;
非唯一索引和無索引的字段,都可能會匹配多條記錄,所以會加 Next-Key Lock。
那如果查詢的 id 存在,還會加 Next-Key Lock 嗎? 比如上面的表,執行 SQL:
SELECT * from t where id = 10 for update;
答案是不會加 Next-Key Lock,這個時候 Next-Key Lock 退化成了行鎖。
查詢語句符合下面三個條件時,Next-Key Lock 會退化成行鎖:
- 查詢使用的是唯一索引;
- SQL 條件是等值匹配(比如 WHERE unique_key = X);
- 掃描到的記錄確定存在。
如果上面 3 個條件有一個不符合 ,都會加 Next-Key Lock。
另一點,雖然 Next-Key Lock 是前開后閉區間,如果索引上等值查詢向右遍歷到最后一個值不滿足等值條件,則退化為間隙鎖。比如執行下面 SQL:
SELECT * from t where id = 8 for update;
Next-Key Lock 退化為間隙鎖,加鎖范圍是(5,10)。id = 10 的這條記錄不影響其他事務修改操作。
優缺點
優點:解決了 REPEATABLE READ 隔離級別下幻讀問題,有效保證了數據一致性。
缺點: 增加了鎖的粒度,一定情況下會降低并發性能,并增加死鎖發生的概率。因為多個事務可能以不同的順序請求對重疊的間隙加鎖,很容易造成死鎖。
下面是一個死鎖的案例,事務 A 準備插入一條(8,8,8)的記錄,事務 B 準備插入一條(9,9,9)的記錄,其他他們相互不影響,但加了 Next-Key Lock,就容易造成死鎖。
事務 A | 事務 B |
select * from t where id = 8 for update; | |
select * from t where id = 9 for update; | |
insert into t values(9,9,9); | |
insert into t values(8,8,8); |
這樣兩個事務都會加(5,10)這個間隙鎖,最終相互等待,形成死鎖。
為什么兩個事務都可以加上間隙鎖,因為間隙鎖之間不會沖突。
總結
Next-Key Lock 是 InnoDB 引擎行鎖加間隙鎖的組合,它鎖住的是一個前開后閉的區間,消除了 REPEATABLE READ 隔離級別下的幻讀問題。理解 Next-Key Lock 要把握兩點:
- 可重復隔離級別下才會生效;
- 鎖住一個前開后閉的區間,但一定條件下可能退化成行鎖或者間隙鎖。