2023-07-03 09:15:44來(lái)源:一樹(shù)一溪
不久之前,有位讀者問(wèn)了一個(gè)關(guān)于insert intention waiting的問(wèn)題,回答過(guò)程中,我還把意向鎖(intention lock)和插入意向鎖(insert intention lock)搞混了,實(shí)際上這是 2 種不同類(lèi)型的鎖。
為此,我研究了下插入意向鎖,于是有了這篇文章。
本文基于 MySQL 8.0.32 源碼,存儲(chǔ)引擎為 InnoDB,事務(wù)隔離級(jí)別為可重復(fù)讀。如需轉(zhuǎn)載,請(qǐng)聯(lián)系『一樹(shù)一溪』公眾號(hào)作者,轉(zhuǎn)載后請(qǐng)標(biāo)明來(lái)源。
(資料圖片僅供參考)
正文
1、什么是插入意向鎖?我們先來(lái)看看官方定義:插入意向鎖是由 INSERT 操作在插入記錄之前加的一種間隙鎖。
官方文檔原文如下:
An insert intention lock is a type of gap lock set by INSERT operations prior to row insertion.
我們?cè)賮?lái)看看插入意向鎖的加鎖代碼:
// 為了方便閱讀,代碼格式做了調(diào)整。// storage/innobase/lock/lock0lock.ccdberr_t lock_rec_insert_check_and_lock(...){ ... const ulint type_mode = LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION; const auto conflicting = lock_rec_other_has_conflicting(type_mode, block, heap_no, trx); ... if (conflicting.wait_for != nullptr) { RecLock rec_lock(thr, index, block, heap_no, type_mode); trx_mutex_enter(trx); err = rec_lock.add_to_waitq(conflicting.wait_for); trx_mutex_exit(trx); } ...}
type_mode 包含了 3 個(gè)標(biāo)志位:
LOCK_X,表示這是個(gè)排他鎖。LOCK_GAP,表示這是個(gè)間隙鎖。LOCK_INSERT_INTENTION,表示這是插入意向鎖。代碼和官方文檔可以相互印證:插入意向鎖是一種排他(LOCK_X)間隙鎖(LOCK_GAP)。
2、為什么需要插入意向鎖?通過(guò)前面的介紹,我們知道了:插入意向鎖本質(zhì)上是間隙鎖。
那么,問(wèn)題來(lái)了:既然有了間隙鎖,那還弄個(gè)插入意向鎖干啥?
答案當(dāng)然是有用了。
有啥用?
說(shuō)來(lái)話長(zhǎng)。
那我們就長(zhǎng)話長(zhǎng)說(shuō),先從間隙鎖說(shuō)起。
我們先來(lái)看一下間隙鎖的特點(diǎn):
間隙鎖的唯一用途是阻止其它事務(wù)插入記錄到間隙中,以實(shí)現(xiàn)可重復(fù)讀。共享間隙鎖、排他間隙鎖的功能完全一樣。間隙鎖可以共存,一個(gè)事務(wù)持有某個(gè)間隙的鎖,該間隙鎖釋放之前,其它事務(wù)也可以申請(qǐng)并獲得該間隙的鎖,并且不區(qū)分共享鎖還是排他鎖。由于多個(gè)間隙鎖可以共存,插入記錄需要加鎖時(shí),如果直接使用間隙鎖,一個(gè)事務(wù)鎖住了某個(gè)間隙,其它事務(wù)執(zhí)行 INSERT 語(yǔ)句還可以插入記錄到該間隙中,也就違背了間隙鎖用于實(shí)現(xiàn)可重復(fù)讀這一特點(diǎn)了。
為了解決這個(gè)問(wèn)題,InnoDB 引入了插入意向鎖。
上一小節(jié),我們從lock_rec_insert_check_and_lock()代碼看到了插入間隙鎖的 type_mode:
const ulint type_mode = LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION;
實(shí)際上,插入意向鎖就是在排他間隙鎖的基礎(chǔ)上打了個(gè)LOCK_INSERT_INTENTION標(biāo)志。
我們通過(guò)具體的應(yīng)用場(chǎng)景來(lái)看一下LOCK_INSERT_INTENTION標(biāo)志的作用機(jī)制:
圖片
事務(wù) T 執(zhí)行 INSERT 語(yǔ)句,插入記錄 R到某個(gè)表的記錄 R1之前。
如果其它事務(wù)對(duì) R1 前面的間隙加了(共享或排他)間隙鎖,事務(wù) T 會(huì)申請(qǐng)對(duì)該間隙加插入意向鎖。
因?yàn)椴迦胍庀蜴i有LOCK_INSERT_INTENTION標(biāo)志,識(shí)別到這個(gè)標(biāo)志,InnoDB 就會(huì)讓 INSERT 語(yǔ)句進(jìn)入等待狀態(tài)。
直到 R1 前面間隙的鎖被釋放,INSERT 語(yǔ)句才能獲得插入意向鎖,插入記錄 R 到 R1 前面的間隙中。
通過(guò)LOCK_INSERT_INTENTION標(biāo)志的介紹可以看到,插入記錄時(shí),只有使用插入意向鎖,其它事務(wù)持有的間隙鎖才能阻止插入操作插入記錄到間隙中。
也就是說(shuō),間隙鎖需要插入意向鎖的配合,才能實(shí)現(xiàn)可重復(fù)讀,這就是為什么需要插入意向鎖的原因了。
3、插入意向鎖和其它鎖的關(guān)系為了介紹這一小節(jié)的內(nèi)容,我們需要先做點(diǎn)準(zhǔn)備工作。
創(chuàng)建測(cè)試表:
USE `test`;CREATE TABLE `t1` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `i1` int DEFAULT "0", PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
插入測(cè)試數(shù)據(jù):
INSERT INTO `t1`(`id`, `i1`)VALUES (10, 101), (20, 201), (30, 301);
數(shù)據(jù)如下:
通過(guò)以下命令創(chuàng)建 3 個(gè)數(shù)據(jù)庫(kù)連接備用:
mysql -h127.0.0.1 -uroot -D test
3 個(gè)連接分別命名為session 1、session 2、session 3。
(1)間隙鎖會(huì)阻塞插入意向鎖我們可以按以下步驟,驗(yàn)證間隙鎖會(huì)阻塞插入意向鎖。
第 1 步,在session 1中執(zhí)行以下 SQL,對(duì)id = 20的記錄加間隙鎖:
BEGIN;SELECT * FROM `t1`WHERE `id` > 10 AND `id` < 20FOR SHARE;
SELECT 語(yǔ)句會(huì)鎖住id = 20的記錄前面的間隙。
第 2 步,在session 2中執(zhí)行以下 SQL,插入一條記錄到id = 20的記錄之前:
BEGIN;INSERT INTO `t1`(`id`, `i1`)VALUES (12, 121);
INSERT 語(yǔ)句發(fā)現(xiàn)id = 20的記錄前面的間隙被鎖住了,會(huì)申請(qǐng)對(duì)該間隙加插入意向鎖,并進(jìn)入等待狀態(tài)。
第 3 步,在session 3中執(zhí)行以下 SQL,查看加鎖情況:
SELECT `engine_transaction_id` as `trx_id`, `object_name` as `table`, `index_name` as `index`, `lock_type`, `lock_mode`, `lock_status`, `lock_data`FROM `performance_schema`.`data_locks`WHERE `lock_type` = "RECORD"AND object_schema = "test";
圖片
通過(guò)加鎖情況,我們可以確認(rèn)間隙鎖會(huì)阻塞插入意向鎖:
session 1的 SELECT 語(yǔ)句持有間隙鎖(lock_mode 包含GAP)。session 2的 INSERT 語(yǔ)句正在等待插入意向鎖(lock_mode 包含INSERT_INTENTION)。最后,在session 1、session 2中執(zhí)行ROLLBACK語(yǔ)句,回滾事務(wù),為后面的驗(yàn)證工作做準(zhǔn)備。
(2)插入意向鎖不會(huì)阻塞間隙鎖我們可以按以下步驟,驗(yàn)證插入意向鎖不會(huì)阻塞間隙鎖。
第 1 步,在session 1中執(zhí)行以下 SQL,對(duì)id = 20的記錄加間隙鎖:
BEGIN;SELECT * FROM `t1`WHERE `id` > 10 AND `id` < 20FOR SHARE;
SELECT 語(yǔ)句會(huì)鎖住id = 20的記錄前面的間隙。
第 1 步加間隙鎖,是為了引發(fā)第 2 步的 INSERT 語(yǔ)句加插入意向鎖。
第 2 步,在session 2中執(zhí)行以下 SQL,插入一條記錄到id = 20的記錄之前:
BEGIN;INSERT INTO `t1`(`id`, `i1`)VALUES (12, 121);
INSERT 語(yǔ)句發(fā)現(xiàn)id = 20的記錄前面的間隙被鎖住了,會(huì)申請(qǐng)對(duì)該間隙加插入意向鎖,并進(jìn)入等待狀態(tài)。
第 3 步,在session 1中執(zhí)行以下 SQL,回滾事務(wù):
ROLLBACK;
SELECT 語(yǔ)句釋放間隙鎖之后,第 2 步session 2中的 INSERT 語(yǔ)句成功獲得插入意向鎖。
第 4 步,在session 1中執(zhí)行以下 SQL,對(duì)id = 20的記錄加間隙鎖:
BEGIN;SELECT * FROM `t1`-- `id` > xx 中的 xx 取值為 15WHERE `id` > 15 AND `id` < 20FOR SHARE;
注意:因?yàn)榈?2 步的 INSERT 語(yǔ)句在id = 10 ~ 20之間插入了id = 12的記錄,第 4 步WHERE 條件id > xx中的xx必須大于12,否則會(huì)觸發(fā)id = 12的記錄上的隱式鎖邏輯,導(dǎo)致 SELECT 語(yǔ)句等待id = 20的記錄上的next-key鎖。
第 5 步,在session 3中執(zhí)行以下 SQL,查看加鎖情況:
SELECT `engine_transaction_id` as `trx_id`, `object_name` as `table`, `index_name` as `index`, `lock_type`, `lock_mode`, `lock_status`, `lock_data`FROM `performance_schema`.`data_locks`WHERE `lock_type` = "RECORD"AND object_schema = "test";
通過(guò)加鎖情況,我們可以確認(rèn)插入意向鎖不會(huì)阻塞間隙鎖:
session 2中,第 2 步的 INSERT 語(yǔ)句持有插入意向鎖(lock_mode 包含INSERT_INTENTION)。session 1中,第 4 步的 SELECT 語(yǔ)句成功獲得了間隙鎖(lock_mode 包含GAP)。最后,在session 1、session 2中執(zhí)行ROLLBACK語(yǔ)句,回滾事務(wù),為后面的驗(yàn)證工作做準(zhǔn)備。
(3)插入意向鎖相互之間不會(huì)阻塞我們可以按以下步驟,驗(yàn)證插入意向鎖相互之間不會(huì)阻塞。
第 1 步,在session 1中執(zhí)行以下 SQL,對(duì)id = 20的記錄加間隙鎖:
BEGIN;SELECT * FROM `t1`WHERE `id` > 10 AND `id` < 20FOR SHARE;
SELECT 語(yǔ)句會(huì)鎖住id = 20的記錄前面的間隙。
執(zhí)行這一步是為了讓第 2、3 步的 INSERT 語(yǔ)句都申請(qǐng)對(duì)id = 20的記錄前面的間隙加插入意向鎖,并進(jìn)入等待狀態(tài)。
第 2 步,在session 2中執(zhí)行以下 SQL,插入一條記錄到id = 20的記錄之前:
BEGIN;INSERT INTO `t1`(`id`, `i1`)VALUES (12, 121);
INSERT 語(yǔ)句發(fā)現(xiàn)id = 20的記錄前面的間隙被鎖住了,會(huì)申請(qǐng)對(duì)該間隙加插入意向鎖,并進(jìn)入等待狀態(tài)。
第 3 步,在session 3中執(zhí)行以下 SQL,插入一條記錄到id = 20的記錄之前:
BEGIN;INSERT INTO `t1`(`id`, `i1`)VALUES (15, 151);
INSERT 語(yǔ)句發(fā)現(xiàn)id = 20的記錄前面的間隙被鎖住了,會(huì)申請(qǐng)對(duì)該間隙加插入意向鎖,并進(jìn)入等待狀態(tài)。
第 4 步,在session 1中執(zhí)行以下 SQL,查看鎖等待情況:
SELECT `engine_transaction_id` as `trx_id`, `object_name` as `table`, `index_name` as `index`, `lock_type`, `lock_mode`, `lock_status`, `lock_data`FROM `performance_schema`.`data_locks`WHERE `lock_type` = "RECORD"AND object_schema = "test";
圖片
由于id = 20的記錄前面的間隙被第 1 步的 SELECT 語(yǔ)句鎖住了,第 2、3 步的 INSERT 語(yǔ)句正在等待該間隙的插入意向鎖。
第 5 步,在session 1中執(zhí)行回滾語(yǔ)句,釋放id = 20的記錄上的間隙鎖:
ROLLBACK;
第 6 步,在session 1中執(zhí)行以下 SQL,查看加鎖情況:
SELECT `engine_transaction_id` as `trx_id`, `object_name` as `table`, `index_name` as `index`, `lock_type`, `lock_mode`, `lock_status`, `lock_data`FROM `performance_schema`.`data_locks`WHERE `lock_type` = "RECORD"AND object_schema = "test";
第 2、3 步的 INSERT 語(yǔ)句同時(shí)獲得了id = 20的記錄前面間隙的插入意向鎖。
通過(guò)加鎖情況,我們可以確認(rèn)插入意向鎖相互之間不會(huì)阻塞。
3.4 next-key 鎖和插入意向鎖會(huì)相互阻塞嗎?對(duì)于 next-key 鎖和插入意向鎖是否會(huì)相互阻塞,這里只給出結(jié)論:
next-key 鎖會(huì)阻塞插入意向鎖。插入意向鎖不會(huì)阻塞 next-key 鎖。感興趣的讀者可以按照 3.1、3.2 小節(jié)的步驟自行測(cè)試,畢竟自己動(dòng)手獲得的知識(shí)才會(huì)記得更牢。
測(cè)試時(shí),需要把 SELECT 語(yǔ)句 WHERE 條件中的id < 20替換為id <= 20,確保 SELECT 語(yǔ)句加的是 next-key 鎖而不是普通的間隙鎖。
4. 怎么知道加了插入意向鎖?我們通過(guò)查詢(xún)performance_schema.data_locks,可以知道某個(gè)事務(wù)是否申請(qǐng)了對(duì)某個(gè)間隙加插入間隙鎖,這種方式我們?cè)谏弦恍」?jié)中已經(jīng)使用過(guò)多次。
如果查詢(xún)結(jié)果中某條記錄的 lock_mode 字段包含INSERT_INTENTION,說(shuō)明對(duì)應(yīng)的事務(wù)申請(qǐng)了加插入意向鎖。
lock_status =WAITING說(shuō)明正在等待插入意向鎖。
lock_status =GRANTED說(shuō)明已經(jīng)獲得了插入意向鎖。
還有一種方式,只能看到正在等待的插入意向鎖,無(wú)法看到已經(jīng)獲得的插入意向鎖。
執(zhí)行SHOW ENGINE InnoDB STATUS語(yǔ)句,部分結(jié)果如下:
-- 為了方便閱讀,對(duì)以下結(jié)果的格式做了調(diào)整-- TRX HAS BEEN WAITING 2 SEC FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 0 page no 47 n bits 72 index PRIMARY of table `test`.`t1` trx id 133955 lock_mode X locks gap before rec insert intention waiting
通過(guò)以上結(jié)果,我們可以得到以下信息:
事務(wù)133955正在等待(waiting)獲取插入意向鎖(insert intention):
lock_mode X對(duì)應(yīng) type_mode 中的LOCK_X。locks gap before rec對(duì)應(yīng) type_mode 中的LOCK_GAP。insert intention對(duì)應(yīng) type_mode 中的LOCK_INSERT_INTENTION。5、總結(jié)在排他(LOCK_X)間隙鎖(LOCK_GAP)的基礎(chǔ)上增加LOCK_INSERT_INTENTION標(biāo)志,就得到了插入意向鎖,所以,從本質(zhì)上來(lái)說(shuō),插入意向鎖是個(gè)特殊的間隙鎖。
間隙鎖需要插入意向鎖的配合,才能阻塞其它事務(wù)插入記錄到某個(gè)間隙中,從而實(shí)現(xiàn)可重復(fù)讀,這就是需要插入意向鎖的原因了。
關(guān)鍵詞:
不久之前,有位讀者問(wèn)了一個(gè)關(guān)于insertintentionwaiting的問(wèn)題,回答過(guò)
JUC-輔助類(lèi)JUC(java util concurrent)是在Java5中引入的一個(gè)并發(fā)編程
為什么要多階段構(gòu)建大家都知道Golang是編譯型語(yǔ)言,源碼需要先編譯再運(yùn)
一、類(lèi)和對(duì)象1、什么是類(lèi)和對(duì)象面向?qū)ο缶幊淌且环N編程范式,它將程序
之前在這篇文章(CSS實(shí)現(xiàn)樹(shù)狀結(jié)構(gòu)目錄[1])中實(shí)現(xiàn)了一個(gè)樹(shù)狀結(jié)構(gòu),效果
需求集中釋放后,市場(chǎng)掉頭向下。
家長(zhǎng)在為孩子選擇國(guó)際學(xué)校綜合考慮的因素很多,比如學(xué)費(fèi)、課程體系、師
陵川補(bǔ)齊農(nóng)村黨建“短板”助推鄉(xiāng)村振興,主流媒體,山西門(mén)戶。山西新聞網(wǎng)
7月3日,信音電子(301329 SZ)開(kāi)啟申購(gòu),發(fā)行價(jià)格為21 00元 股,申購(gòu)上
新華社羅馬7月2日電(國(guó)際觀察)全球糧農(nóng)事業(yè)發(fā)展的中國(guó)貢獻(xiàn)新華社記者
火箭在幾天前還在追求天賦,在幾天后立刻放棄了天賦?,老鷹,天賦,火箭
1、手上起小紅疙瘩很癢,有以下幾種可能:1??赡苁墙戬?,主要由疥瘡感
證券時(shí)報(bào)網(wǎng)訊,7月2日,永泰能源(600157)發(fā)布公告,公司核心管理人員將
本周觀點(diǎn):本周酒鬼酒、古井貢酒、瀘州老窖召開(kāi)2022年度股東大會(huì),整體
證券時(shí)報(bào)e公司訊,近日,萬(wàn)興科技(300624)旗下億圖腦圖正式上線AI繪畫(huà)