十四、mysql改一條記錄卻鎖住多行數據的原因

目錄

加鎖規則的兩個“原則”、兩個“優化”和一個“bug”

案例一:等值查詢間隙鎖

案例二:非唯一索引等值鎖

案例三:主鍵索引範圍鎖

案例四、非唯一索引範圍鎖

案例五:唯一索引範圍鎖bug

案例六、非唯一索引上 存在“等值”的例子

案例七:limit語句加鎖

案例八:一個死鎖的例子

總結


這裏需要明確間隙鎖和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語句加鎖

 

 


案例八:一個死鎖的例子

 


總結

發佈了102 篇原創文章 · 獲贊 14 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章