1.? 基礎(chǔ)知識回顧
1、索引的有序性,索引本身就是有序的
2、InnoDB中間隙鎖的唯一目的是防止其他事務(wù)插入間隙。間隙鎖可以共存。一個事務(wù)取得的間隙鎖并不會阻止另一個事務(wù)取得同一間隙上的間隙鎖。共享和獨(dú)占間隔鎖之間沒有區(qū)別。它們彼此之間不沖突,并且執(zhí)行相同的功能。
3、MySQL默認(rèn)隔離級別是 REPEATABLE-READ
4、加鎖的對象是索引,加鎖的基本單位是next-key鎖,而行鎖和間隙鎖,是由next-key鎖退化而來的
5、記錄鎖,鎖的是索引,而非數(shù)據(jù)本身
6、間隙鎖是開區(qū)間,next-key鎖是前開后閉區(qū)間
7、意向鎖是表級鎖,它相當(dāng)于一個標(biāo)志,可以用來提高加鎖的效率
8、間隙鎖的目的是為了防止幻讀,在“讀已提交”隔離級別下允許幻讀,所以如果隔離級別是“讀已提交”,就不會用到間隙鎖,更不會用到next-key鎖。因此,只有“可重復(fù)讀”及以上隔離級別下,才會有next-key鎖
9、InnoDB中鎖住的是索引。對輔助索引加鎖時,輔助索引所對應(yīng)的主鍵索引也會被鎖住。
10、所謂“間隙”本質(zhì)是又間隙右邊的那條記錄決定的
?
接下來,具體看一下走不同的索引時的加鎖情況。本例中使用的MySQL版本為8.0.30
SELECT VERSION();
SHOW VARIABLES LIKE 'transaction_isolation';
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
測試表結(jié)構(gòu)及數(shù)據(jù)如下:
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)
注意,是開區(qū)間,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)
鎖定區(qū)間:[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鎖是前開后閉區(qū)間,所以,最終鎖定區(qū)間為:[10,20]
如果這里不是id>=10,而是id>10的話,最終只會在id=20上加next-key鎖,這種情況下鎖定區(qū)間為:(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加記錄鎖
情況二:等值查詢,不存在
先看一眼現(xiàn)在的數(shù)據(jù)
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')
索引是有序的,盡管索引字段類型是字符串類型,仍然是有序的
因?yàn)槭情g隙鎖,所以沒有鎖定1050,也就自然不會給id=50加記錄鎖
值得注意的是,在('1040', '1050')這個區(qū)間內(nèi)插入是不行的,但是更新是可以的
情況三:范圍查找
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'上加間隙鎖
加鎖區(qū)間:(-∞, 2023-12-01]、(2023-12-01, 2023-12-12)、id=10
因?yàn)槭欠俏ㄒ凰饕?,所以?dāng)找到第一條birthday = '2023-12-01'的記錄時,不確定后面還有沒有這樣的記錄,所以必須繼續(xù)往后找,直到遇到一條不是2023-12-01的記錄未止。
間隙鎖阻止其它事務(wù)插入,但是不阻止更新
情況二:范圍查找
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.? 總結(jié)
1、主鍵索引
- 等值查詢,命中,則被命中的主鍵索引加記錄鎖
- 等值查詢,未命中,則繼續(xù)向后(向右)查找,直到找到第一個不滿足的記錄,對該記錄加間隙鎖,即鎖住該記錄之前的間隙,以防止其它事務(wù)向其中插入數(shù)據(jù)
- 范圍查找,找到的(滿足條件的)記錄的主鍵加記錄鎖,掃描過的區(qū)間加間隙鎖
2、非主鍵唯一索引
- 與主鍵索引類似,唯一的區(qū)別是鎖住輔助索引記錄的同時會鎖住對應(yīng)的主鍵索引
3、非唯一索引
- 向右查找直到遇到一條不滿足條件的記錄,然后對掃描到的區(qū)間加間隙鎖,對掃描到的輔助索引記錄加記錄鎖,同時對與其對應(yīng)的主鍵加記錄鎖
4、不走索引
- 表中所有記錄的主鍵加next-key鎖
?
總結(jié)幾個規(guī)律:
- 命中的索引記錄會加記錄鎖,如果它是一個輔助索引,則對應(yīng)的主鍵索引也會被加上記錄鎖
- 沒有命中的記錄不會被加記錄鎖
- 非唯一索引上查找時,當(dāng)找到第一條滿足條件的索引記錄時,還會繼續(xù)向右查找,直到遇到一條不滿足條件的記錄(PS:幸虧索引是有序的,不然找到累死)
- 當(dāng)一條SQL沒有走索引時,那么將會在每一條聚集索引上加X鎖,這個類似于表鎖,但原理上和表鎖是完全不同的
建議:
- 盡量控制事務(wù)大小,減少鎖定資源量和時間長度
- 即便在條件中使用了索引字段,但是否使用索引來檢索數(shù)據(jù)是由 MySQL 通過判斷不同執(zhí)行計(jì)劃的代價來決定的。如果 MySQL 認(rèn)為全表掃描效率更高,它就不會使用索引。因此,在分析鎖沖突時,可以查看執(zhí)行計(jì)劃(explain)以確認(rèn)是否真正使用了索引
最后,重要的事情說三遍:
- 加鎖的單位是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文章來源:http://www.zghlxwxcb.cn/news/detail-760306.html
https://cloud.tencent.com/developer/article/1844928文章來源地址http://www.zghlxwxcb.cn/news/detail-760306.html
到了這里,關(guān)于MySQL InnoDB加鎖規(guī)則分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!