MySQL InnoDB加鎖規則分析

1.  基礎知識回顧

1、索引的有序性,索引本身就是有序的

2、InnoDB中間隙鎖的唯一目的是防止其他事務插入間隙。間隙鎖可以共存。一個事務取得的間隙鎖並不會阻止另一個事務取得同一間隙上的間隙鎖。共享和獨佔間隔鎖之間沒有區別。它們彼此之間不衝突,並且執行相同的功能。

3、MySQL默認隔離級別是 REPEATABLE-READ

4、加鎖的對象是索引,加鎖的基本單位是next-key鎖,而行鎖和間隙鎖,是由next-key鎖退化而來的

5、記錄鎖,鎖的是索引,而非數據本身

6、間隙鎖是開區間,next-key鎖是前開後閉區間

7、意向鎖是表級鎖,它相當於一個標誌,可以用來提高加鎖的效率

8、間隙鎖的目的是爲了防止幻讀,在“讀已提交”隔離級別下允許幻讀,所以如果隔離級別是“讀已提交”,就不會用到間隙鎖,更不會用到next-key鎖。因此,只有“可重複讀”及以上隔離級別下,纔會有next-key鎖

9、InnoDB中鎖住的是索引。對輔助索引加鎖時,輔助索引所對應的主鍵索引也會被鎖住。

10、所謂“間隙”本質是又間隙右邊的那條記錄決定的

 

接下來,具體看一下走不同的索引時的加鎖情況。本例中使用的MySQL版本爲8.0.30

SELECT VERSION();
SHOW VARIABLES LIKE 'transaction_isolation';
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

測試表結構及數據如下:

2.  案例分析

LOCK_MODE不同值的含義:

  • X :代表next-key鎖
  • X,GAP :代表間隙鎖
  • X,REC_NOT_GAP :代表記錄鎖

2.1.  主鍵索引

情況一:等值查詢,存在

Session A Session B
BEGIN;
SELECT * FROM t_user WHERE id = 10 FOR UPDATE;
 
 

INSERT INTO t_user (id, `name`, id_card_no, birthday, score) VALUES (9, '于禁', '1012', '2023-11-01', 1);

Affected rows: 1

首先對錶加意向排它鎖,然後對主鍵加記錄鎖,可以看到只鎖住了id=10這個主鍵索引

情況二:等值查詢,不存在

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE id = 5 FOR UPDATE;
   
 

INSERT INTO t_user (id, `name`, id_card_no, birthday, score) VALUES (6, '于禁', '1012', '2023-11-01', 1);

1205 - Lock wait timeout exceeded; try restarting transaction

 
   

UPDATE t_user SET score = score + 1 WHERE id = 10;

Affected rows: 1

加鎖範圍: (-∞, 10)

注意,是開區間,10並沒有被鎖

情況三:範圍查找

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE id >= 10 AND id < 11 FOR UPDATE;
   
 

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (9,'典韋','1011','2022-12-19',1)

Affected rows: 1

 
   

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (18,'徐晃','1018','2022-12-09',1);

1205 - Lock wait timeout exceeded; try restarting transaction

一個記錄鎖10,加一個間隙鎖(10, 20),合起來就是[10, 20)

鎖定區間:[10, 20)

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE id >= 10 AND id <= 20 FOR UPDATE;
   
 

UPDATE t_user SET score = score + 1 WHERE id = 20;

1205 - Lock wait timeout exceeded; try restarting transaction

 
   

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (21,'張遼','1021','2022-12-09',1);

Affected rows: 1

id=10上加了記錄鎖,id=20上加了next-key鎖

next-key鎖是前開後閉區間,所以,最終鎖定區間爲:[10,20]

如果這裏不是id>=10,而是id>10的話,最終只會在id=20上加next-key鎖,這種情況下鎖定區間爲:(10,20]

2.2.  唯一索引(非主鍵)

情況一:等值查詢,存在

Session A Session B
BEGIN;
SELECT * FROM t_user WHERE id_card_no = '1003' FOR UPDATE;
 
 

UPDATE t_user SET score = score + 1 WHERE id = 30;

1205 - Lock wait timeout exceeded; try restarting transaction

輔助索引 ('1003',30)加記錄鎖,同時,主鍵索引上id=30加記錄鎖

情況二:等值查詢,不存在

先看一眼現在的數據

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE id_card_no = '1042' FOR UPDATE;
   
 

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (52,'許褚','1041','2023-01-01',1);

1205 - Lock wait timeout exceeded; try restarting transaction

 
   

UPDATE t_user SET score = score + 1 WHERE id_card_no = '1041';

Affected rows: 0

只在輔助索引idx_card上加了間隙鎖,鎖定範圍是:('1040', '1050')

索引是有序的,儘管索引字段類型是字符串類型,仍然是有序的

因爲是間隙鎖,所以沒有鎖定1050,也就自然不會給id=50加記錄鎖

值得注意的是,在('1040', '1050')這個區間內插入是不行的,但是更新是可以的

情況三:範圍查找

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE id_card_no <= '1024' FOR UPDATE;
   
 

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (11,'潘鳳','1011','2023-01-01',1);

1205 - Lock wait timeout exceeded; try restarting transaction

 
   

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (11,'潘鳳','1031','2023-01-01',1);

Affected rows: 1

主鍵索引上id=10和id=20都加了記錄鎖

輔助索引idx_card上加了Next-key鎖,鎖定範圍爲:(-∞, '1010']、('1010', '1020']、('1020', '1030']

2.3.  非唯一索引(普通索引)

情況一:等值查詢,存在

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE birthday = '2023-12-01' FOR UPDATE;
   
 

UPDATE t_user SET score = score + 1 WHERE birthday = '2023-12-11';

Affected rows: 0

 
   

UPDATE t_user SET score = score + 1 WHERE birthday = '2023-12-09';

Affected rows: 0

主鍵索引id=10加記錄鎖

輔助索引idx_birthday上,'2023-12-01'上加Next-key鎖,'2023-12-12'上加間隙鎖

加鎖區間:(-∞, 2023-12-01]、(2023-12-01, 2023-12-12)、id=10

因爲是非唯一索引,所以當找到第一條birthday = '2023-12-01'的記錄時,不確定後面還有沒有這樣的記錄,所以必須繼續往後找,直到遇到一條不是2023-12-01的記錄未止。

間隙鎖阻止其它事務插入,但是不阻止更新

情況二:範圍查找

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE birthday >= '2023-11-11' AND birthday <='2023-11-28' FOR UPDATE;
   
 

UPDATE t_user SET score = score + 1 WHERE birthday = '2023-11-29';

Affected rows: 0

 
   

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (13,'華雄','1033','2023-11-29',1);

1205 - Lock wait timeout exceeded; try restarting transaction

主鍵索引上加鎖範圍:id=30和id=40

輔助索引idx_birthday上加鎖範圍:(2023-01-01, 2023-11-12]、(2023-11-12, 2023-11-28]、(2023-11-28, 2023-11-30]

2.4.  不走索引

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE score = 2 FOR UPDATE;
   
 

UPDATE t_user SET score = score + 1 WHERE id = 33;

Affected rows: 0

 
   

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (33,'顏良','1038','2023-12-20',1);

Lock wait timeout exceeded; try restarting transaction

在所有記錄的主鍵上加next-key鎖

加鎖範圍:(-∞, 10]、(10, 20]、(20, 30]、(30, 40]、(40, 50]、(50, +∞)

3.  總結

1、主鍵索引

  • 等值查詢,命中,則被命中的主鍵索引加記錄鎖
  • 等值查詢,未命中,則繼續向後(向右)查找,直到找到第一個不滿足的記錄,對該記錄加間隙鎖,即鎖住該記錄之前的間隙,以防止其它事務向其中插入數據
  • 範圍查找,找到的(滿足條件的)記錄的主鍵加記錄鎖,掃描過的區間加間隙鎖

2、非主鍵唯一索引

  • 與主鍵索引類似,唯一的區別是鎖住輔助索引記錄的同時會鎖住對應的主鍵索引

3、非唯一索引

  • 向右查找直到遇到一條不滿足條件的記錄,然後對掃描到的區間加間隙鎖,對掃描到的輔助索引記錄加記錄鎖,同時對與其對應的主鍵加記錄鎖

4、不走索引

  • 表中所有記錄的主鍵加next-key鎖

 

總結幾個規律:

  1. 命中的索引記錄會加記錄鎖,如果它是一個輔助索引,則對應的主鍵索引也會被加上記錄鎖
  2. 沒有命中的記錄不會被加記錄鎖
  3. 非唯一索引上查找時,當找到第一條滿足條件的索引記錄時,還會繼續向右查找,直到遇到一條不滿足條件的記錄(PS:幸虧索引是有序的,不然找到累死)
  4. 當一條SQL沒有走索引時,那麼將會在每一條聚集索引上加X鎖,這個類似於表鎖,但原理上和表鎖是完全不同的

建議:

  1. 儘量控制事務大小,減少鎖定資源量和時間長度
  2. 即便在條件中使用了索引字段,但是否使用索引來檢索數據是由 MySQL 通過判斷不同執行計劃的代價來決定的。如果 MySQL 認爲全表掃描效率更高,它就不會使用索引。因此,在分析鎖衝突時,可以查看執行計劃(explain)以確認是否真正使用了索引

最後,重要的事情說三遍:

  • 加鎖的單位是next-key鎖
  • 加鎖的單位是next-key鎖
  • 加鎖的單位是next-key鎖

 

參考

https://www.cnblogs.com/harda/p/16820592.html

https://blog.csdn.net/qq_42604176/article/details/115431744

https://zhuanlan.zhihu.com/p/378306056

https://cloud.tencent.com/developer/article/1971381

https://cloud.tencent.com/developer/article/1844928

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章