目錄
這裏需要明確間隙鎖和next-key lock的概念及加鎖規則。
加鎖規則有以下兩條前提說明:
1、MySQL後面的版本可能會改變加鎖策略,以下規則限於5.x系列<=5.7.24,8.0系列<=8.0.13.
2、間隙鎖在可重複讀隔離級別下才有效。
加鎖規則的兩個“原則”、兩個“優化”和一個“bug”
1、原則1:加鎖的基本單位是next-key lock(前開後閉區間);
2、原則2:查找過程中訪問到的對象纔會加鎖;
3、優化1:索引上的等值查詢,給唯一索引加鎖的時候,next-key lock退化爲行鎖;
4、優化2:索引上的等值查詢,向右遍歷時且最後一個值不滿足等值條件的時候,next-key lock退化爲間隙鎖;
5、一個bug:唯一索引上的範圍查詢會訪問到不滿足條件的第一個值爲止。
案例一:等值查詢間隙鎖
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
1、根據原則1,加鎖單位是next-key lock,session A加鎖範圍就是(5,10];
2、同時根據優化2,這是一個等值查詢(id=7),而id=10不滿足查詢條件,next-key lock退化成間隙鎖,因此最終加鎖的範圍是(5,10)。
所以,session B要往這個間隙裏面插入id=8的記錄會被鎖住,但是session C修改id=10這行駛可以的。
案例二:非唯一索引等值鎖
這裏session A要給索引c上c=5的這一行加上讀鎖。
1、根據原則1,加鎖單位是next-key lock,因此會給(0,5]加上next-key lock;
2、c是普通索引,因此僅訪問c=5這一條記錄是不能馬上停下來的,需要向右遍歷,查到c=10才放棄。根據原則2,訪問到的都要加鎖,因此要給(5,10】加next-key lock。
3、同時這個符合優化2:等值判斷,向右遍歷,最後一個值不滿足c=5這個等值條件,因此退化成間隙鎖(5,10).
4、根據原則2,只有訪問到的對象纔會加鎖,這個查詢使用覆蓋索引,並不需要訪問主鍵索引,所以主鍵索引上沒有加任何鎖,這就是爲什麼session B的update語句可以執行完成。
但session C要插入一個(7,7,7)的記錄,就會被session A的間隙鎖(5,10)鎖住。
需要注意,這個例子中,lock in share mode只鎖覆蓋索引,但是如果是for update 就不一樣了。執行for update 時,系統會認爲你接下來要更新數據,因此會順便給主鍵索引上滿足條件的行加上行鎖。
這個例子說明,鎖是加在索引上的;同時,它給我們的指導是,如果你要用lock in share mode來給行加讀鎖避免數據被更新的話,就必須得繞過覆蓋索引的優化,在查詢字段中鍵入索引中不存在的字段。比如,將session A的查詢語句改成select d from t where c=5 lock in share mode。
案例三:主鍵索引範圍鎖
關於範圍查詢
mysql> select * from t where id=10 for update;
mysql> select * from t where id>=10 and id<11 for update;
在邏輯上,這兩條查詢語句肯定是等價的,但是加鎖規則不太一樣 。
1、開始執行的時候,要找到第一個id=10的行,因此本該是next-key lock(5,10]。根據優化1,主鍵id 上的等值條件,退化成行鎖,只加了id=10這一行的行鎖。
2、範圍查找就往後繼續找,找到id=15這一行停下來,因此 需要加next-key lock(10,15].
所以,session A 這時候鎖的範圍就是主鍵索引上,行鎖id=10和next-key lock(10,15].這樣,session B和session C的結果就能理解了。
這裏需要注意一點,首次session A定位查找id=10的行的時候,是當做等值查詢來判斷的,而向右掃描到id=15的時候,用的是範圍查詢判斷。
案例四、非唯一索引範圍鎖
這次session A 用字段c來判斷,加鎖規則和案例三唯一的不同是:在第一次用c=10定位記錄的時候,索引c上加了(5,10]這個next-key lock後,由於索引c 是非唯一索引,沒有優化規則,也就是不會蛻變爲行鎖,因此最終session A加的鎖死,索引上的(5,10]和(10,15]這兩個next-key lock。
所以從結果上看,session B要插入(8,8,8)的這個insert 語句時就被堵住了。
這裏需要掃描到c=15才停止掃描,是合理的,因爲InnoDB要掃到c=15,才知道不需要繼續往後找了。
案例五:唯一索引範圍鎖bug
案例六、非唯一索引上 存在“等值”的例子
案例七:limit語句加鎖
案例八:一個死鎖的例子