- 因?yàn)檫@里描述全部是Innodb的行鎖,為了方便描述,這里將語(yǔ)句需要的鎖資源描述為請(qǐng)求行鎖,而已經(jīng)獲取或者等待的行鎖描述為當(dāng)前行鎖。
- 其次鎖結(jié)構(gòu)對(duì)于行鎖來(lái)講和page綁定,一個(gè)鎖結(jié)構(gòu)可以用多個(gè)bit位表示一個(gè)page的多行記錄鎖,每個(gè)bit代表一行記錄。但是這需要滿足一定的條件,下面描述。
- 這里討論也是慢速加鎖(lock_rec_lock_slow)的情況,不考慮快速加鎖。
一、 問(wèn)題所在和BUG修復(fù)
這個(gè)問(wèn)題大概如下,也是最近看到很多朋友在討論,也遇到過(guò)。我們使用8023和8036分別測(cè)試,表結(jié)構(gòu)如下,隔離級(jí)別RC。
mysql> show create table test \G
*************************** 1. row ***************************
Table: test
Create Table: CREATE TABLE `test` (
`id` int NOT NULL,
`a` varchar(20) DEFAULT NULL,
`b` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
mysql> select * from test;
+----+------+------+
| id | a | b |
+----+------+------+
| 1 | g | g |
| 20 | p | p |
| 30 | l | l |
+----+------+------+
3 rows in set (0.00 sec)
TRX1 | TRX2 |
---|---|
begin; | |
select * from test where id=20 for share; | |
select * from test where id=20 for update;(堵塞) | |
select * from test where id=20 for update; | |
8023死鎖檢測(cè),8036繼續(xù)堵塞 |
這個(gè)問(wèn)題最終被計(jì)入了BUG,看起來(lái)這個(gè)BUG由來(lái)已久,如下以下
- https://bugs.mysql.com/bug.php?id=105655
- https://bugs.mysql.com/bug.php?id=101695
- https://bugs.mysql.com/bug.php?id=21356
修復(fù)版本為8029,修復(fù)點(diǎn)很多,這里重點(diǎn)關(guān)注下為什么8036不會(huì)造成死鎖,其他的暫時(shí)不做分析。還需要注意在BUG 101695中可以看出這個(gè)問(wèn)題并不一定出現(xiàn)在這種構(gòu)造的場(chǎng)景中,在并發(fā)的update,且包含unique索引,做唯一性檢查的是否也有可能出現(xiàn),但是我暫時(shí)沒(méi)有構(gòu)造出來(lái)必現(xiàn)的場(chǎng)景,如果需要模擬可以參考這個(gè)BUG中的描述。這種死鎖在死鎖日志有2個(gè)特點(diǎn),
- 某個(gè)TRANSACTION 的HOLDS THE LOCK(S)和WAITING FOR THIS LOCK TO BE GRANTED 記錄是完全一樣的,這是因?yàn)檫@個(gè)鎖結(jié)構(gòu)既被堵塞了,同時(shí)也堵塞了其他鎖結(jié)構(gòu)。這種環(huán)形等待通過(guò)一個(gè)叫做outgoing的容器很容易檢測(cè)出來(lái),這個(gè)容器大概如下,
outgoing容器:
outgoing[0] = 3(info容器下標(biāo)) outgoing[0] [A] wait [D] D為堵塞者
outgoing[1] = 0(info容器下標(biāo)) outgoing[1] [B] wait [A] A為堵塞者
outgoing[2] = 0(info容器下標(biāo)) outgoing[2] [C] wait [A] A為堵塞者
outgoing[3] = 0(info容器下標(biāo)) outgoing[3] [D] wait [A] A為堵塞者
outgoing[4] = 0(info容器下標(biāo)) outgoing[4] [F] wait [A] A為堵塞者
A
| \ |\ |\
| | |/
B C D
$60 = std::vector of length 4, capacity 4 = {3, 0, 0, 0}
然后根據(jù)環(huán)形堵塞的兩個(gè)事務(wù)獲取其堵塞和被堵塞的鎖結(jié)構(gòu)就可以了。
- 同時(shí)也包含了S lock,在RC模式下通常就是唯一性檢查或 for share訪問(wèn)數(shù)據(jù)。
二、行鎖的type_mode
innodb 行鎖的type_mode由很多位表示,出現(xiàn)在lock_t(鎖結(jié)構(gòu))的type_mode主要定義有如下,
LOCK_MODE_MASK: 0XF(低4位),本文描述為L(zhǎng)OCK_MODE
LOCK_IS : 0
LOCK_IX :1
LOCK_S :2
LOCK_X :3
LOCK_AUTO_INC :4LOCK_TYPE_MASK:0XF0,本文描述為L(zhǎng)OCK_TYPE
LOCK_TABLE = 16 (表鎖)
LOCK_REC = 32(記錄鎖)其他屬性:
LOCK_WAIT:256
LOCK_ORDINARY :0(next key lock)
LOCK_GAP:512(gap lock)
LOCK_REC_NOT_GAP:1024(key lock)
LOCK_INSERT_INTENTION:2048(插入映像鎖)
這里我們通常用注意到底4位就是LOCK_MODE,而LOCK_TYPE因?yàn)橥ǔ6际怯懻撔墟i,因此都是LOCK_REC,在其他屬性中包含了是否事gap lock/next key lock/key lock等,也包含了是否處于等待。比如一個(gè)type_mode為1058的鎖實(shí)際上就是,
LOCK_REC_NOT_GAP|LOCK_S 且是LOCK_REC(記錄鎖)
三、關(guān)于記錄鎖的hash結(jié)構(gòu)
當(dāng)每一個(gè)需要的請(qǐng)求行鎖都需要放到一個(gè)hash結(jié)構(gòu)中,不管是否獲取成功,這個(gè)hash結(jié)構(gòu)就是通過(guò)通過(guò)加鎖記錄所在的space id和page no進(jìn)行hash計(jì)算的(m_fold(lock_rec_fold(page_id)),也就是說(shuō)同一個(gè)表的同一個(gè)page no上的不同鎖結(jié)構(gòu)會(huì)放到一個(gè)cell種,雖然在事務(wù)中也會(huì)存在本事務(wù)持有的全表鎖結(jié)構(gòu),但是如果需要跨事務(wù)查找鎖結(jié)構(gòu)的時(shí)候就需要用到這個(gè)hash結(jié)構(gòu),比如鎖沖突檢測(cè)。當(dāng)然這里要分2種情況條論,
- 如果請(qǐng)求行鎖需要等待,則新建鎖結(jié)構(gòu),放到cell鏈表的末尾,參考函數(shù)lock_rec_insert_to_waiting
- 如果請(qǐng)求行鎖不需要等待,獲取成功,那么需要判斷是否有現(xiàn)有的鎖結(jié)構(gòu)能夠容下本次獲取的行鎖,滿足條件:
A.事務(wù)是同一個(gè)
B.LOCK_MODE/LOCK_TYPE/其他屬性 均相同
C.當(dāng)前鎖結(jié)構(gòu)的bit位能夠容下
就會(huì)合并到一個(gè)鎖結(jié)構(gòu)中,當(dāng)然沒(méi)有滿足條件的鎖結(jié)構(gòu),那么就需要新建了,然后放到cell鏈表的頭部,參考函數(shù)(lock_rec_add_to_queue)
這個(gè)hash 結(jié)構(gòu)我們可以簡(jiǎn)單的描述如下。
space_id/page_no
cell | ->lock_t(heap no 5,7)->lock_t(heap no 6)->lock_t(waiting heap no 5)->lock_t(waiting heap no 6)
cell | ->lock_t(heap no 10,11) ->lock_t(heap no 11)
cell | ->lock_t(heap no 7)
四、請(qǐng)求行鎖和本事務(wù)當(dāng)前行鎖的比對(duì)
這個(gè)判斷主要是調(diào)用lock_rec_has_expl函數(shù)進(jìn)行一次判斷,主要判斷是否本trx是否已經(jīng)獲取了跟關(guān)于本行鎖更高級(jí)或者相等的LOCK_MODE,這樣就可以直接獲取成功了,不用做什么了。
而這個(gè)判斷主要遍歷剛才說(shuō)的hash結(jié)構(gòu)的cell,每一個(gè)鎖結(jié)構(gòu)(lock_t),先判定是否本行加鎖的結(jié)構(gòu),然后通過(guò)一個(gè)叫做STRONGER-OR-EQUAL的矩陣(注意不是兼容矩陣)進(jìn)行判斷,主要的判定流程大概有如下,如果找到一個(gè)符合條件的鎖結(jié)構(gòu)就返回,
- 1:當(dāng)前行鎖的記錄必須和請(qǐng)求行鎖的記錄相同,也就是包含了同一行記錄,這個(gè)是在迭代的條件中判斷的
- 2:當(dāng)前行鎖的事務(wù)ID和請(qǐng)求行鎖的事務(wù)ID相同
- 3:確認(rèn)不是插入印象鎖
- 4:找到的鎖沒(méi)有處于等待,這個(gè)條件8036有變動(dòng),但是整體邏輯看起來(lái)沒(méi)變
- 5:STRONGER-OR-EQUAL 矩陣判斷 lock S / lock X 是否兼容,下面是這個(gè)矩陣(注意不是兼容矩陣)
/* STRONGER-OR-EQUAL RELATION (mode1=row, mode2=column)
* IS IX S X AI
* IS + - - - -
* IX + + - - -
* S + - + - -
* X + + + + +
* AI - - - - +
* See lock_mode_stronger_or_eq().
這里我們關(guān)注S和X就可以了,后面還會(huì)用到,
* S X
* S + -
* X + +
可以看到如果是已經(jīng)獲取了S,那么本次如果獲取S則是TRUE,如果是X則是FALSE,如果已經(jīng)獲取了X,那么本次如果獲取S則是TRUE,如果是X則也是TRUE。
- 6: 是sup偽列 或者 (條件X),這里條件X比較復(fù)雜簡(jiǎn)單推了一下如下,
A:當(dāng)前行鎖是key lock或next key lock不是gap lock ,請(qǐng)求行鎖是gap lock 不是key lock或next key lock ,返回false (1 || !0 && 0||!1 = 0)
B:當(dāng)前行鎖是key lock或next key lock不是gap lock ,請(qǐng)求行鎖是key lock或next key lock不是gap lock,返回true (1 || !1 && 0||!0 = 1)
C:當(dāng)前行鎖是gap lock不是key lock或next key lock ,請(qǐng)求行鎖是gap lock不是key lock或next key lock,返回true (0 || !0 && 1|| !1 = 1)
D:當(dāng)前行鎖是gap lock不是key lock或next key lock ,請(qǐng)求行鎖是key lock或next key lock不是gap lock,返回false (0||!1 && 1|| !0 =0)
五、請(qǐng)求行鎖和其他事務(wù)是否沖突
這里主要判定是請(qǐng)求行鎖和其他事務(wù)的行鎖是否有沖突,如果有沖突就需要等待了,這里同樣和上面一樣是通過(guò)變量hash結(jié)構(gòu)的cell來(lái)進(jìn)行的,也都需要是包含本記錄的鎖結(jié)構(gòu)才會(huì)判斷,同樣是在所有的鎖結(jié)構(gòu)中找到一個(gè)有沖突的就可以了,否則就是沒(méi)有沖突。這里主要使用的是兼容矩陣進(jìn)行判斷,這個(gè)過(guò)程在lock_rec_other_has_conflicting中,其中8036的判定的函數(shù)為 locksys::rec_lock_check_conflict,其中包含如下條件,
- 當(dāng)前行鎖的記錄必須和請(qǐng)求行鎖的記錄相同,也就是包含了同一行記錄,這個(gè)是在迭代的條件中判斷的
- 如果當(dāng)前行鎖的鎖結(jié)構(gòu)的事物ID和請(qǐng)求行鎖的事物ID相同 或者 請(qǐng)求行鎖鎖結(jié)構(gòu)的LOCK_MODE和當(dāng)前行鎖的鎖結(jié)構(gòu)的 LOCK_MODE 無(wú)沖突,這里判斷是通過(guò)兼容性矩陣進(jìn)行的,也是比較簡(jiǎn)單,兼容性矩陣如下,
/* LOCK COMPATIBILITY MATRIX
IS IX S X AI
IS + + + - +
IX + + - - +
S + - + - -
X - - - - -
AI + + - - -
*
這里重點(diǎn)關(guān)注S 和X LOCK_MODE。
- 如果本事務(wù)是高權(quán),但是當(dāng)前行鎖的鎖結(jié)構(gòu)處于等待且當(dāng)前行鎖的持有事務(wù)不是高權(quán)限(SQL線程)
- 如果本行記錄是sup列 或者 請(qǐng)求行鎖是gap lock, 并且不是 插入印象鎖
- 請(qǐng)求行鎖不是插入映像鎖且當(dāng)前行鎖結(jié)構(gòu) gap lock
- 請(qǐng)求行鎖是gap lock,但是當(dāng)前行鎖是 非gap lock
- 如果當(dāng)前行鎖是插入映像鎖
都判定為沒(méi)有沖突,這里我們可以理解主要是第2點(diǎn)進(jìn)行了LOCK_MODE的判斷,而第3點(diǎn)則是保證主從中的SQL線程獲取的鎖結(jié)構(gòu)有更高的優(yōu)先級(jí),但是SQL線程多個(gè)worker之間由于都具有高優(yōu)先級(jí)則回歸到普通鎖結(jié)構(gòu)的判斷規(guī)則。其他點(diǎn)都是比較特殊的情況,作為補(bǔ)充,主要是對(duì)gap lock的補(bǔ)充。
而到了本BUG修復(fù)后,比如8.0.36這里多了一個(gè)判斷條件也就是,如果檢測(cè)通過(guò)也可以不堵塞,這也是這個(gè)例子為什么8.0.36不會(huì)觸發(fā)死鎖的根本原因。也就是這個(gè)條件導(dǎo)致了前面案例中,級(jí)別TRX2 由于獲取LOCK_MODE X 處于LOCK_WAIT,而TRX1再次獲取本行記錄的LOCK_MODE X的時(shí)候還能夠獲取到,而不會(huì)被TRX2堵塞。這個(gè)條件有如下一些先決條件,
if (!(type_mode & LOCK_INSERT_INTENTION) && lock2->is_waiting() &&
lock2->mode() == LOCK_X && (type_mode & LOCK_MODE_MASK) == LOCK_X) { // A.如果不是插入映像鎖
// We would've already returned false if it was a gap lock. // B. 當(dāng)前的鎖處于waitting狀態(tài)
ut_ad(!(type_mode & LOCK_GAP)); // C. 當(dāng)前的鎖是lock X
// Similarly, since locks on supremum are either LOCK_INSERT_INTENTION or // D. 需要獲取的鎖模式是 lock x
// gap locks, we would've already returned false if it's about supremum. //言外之意 這需要額外的判定
..
if (trx_locks_cache.has_granted_blocker(trx, lock2)) { //這里帶入的是本次的事務(wù),而lock2為當(dāng)前鎖結(jié)構(gòu)
return Conflict::CAN_BYPASS;
}
主要就是locksys::Trx_locks_cache::has_granted_blocker,這個(gè)函數(shù)帶入了本次請(qǐng)求行鎖的事務(wù)和當(dāng)前行鎖的鎖結(jié)構(gòu)。先決條件中有當(dāng)前行鎖處于waitting狀態(tài)的條件,也就像例子中處于等待狀態(tài)的TRX2的那個(gè)行鎖結(jié)構(gòu)就是這樣的。
接下來(lái)這個(gè)函數(shù)通過(guò)構(gòu)造一個(gè)LOCK_S | LOCK_REC_NOT_GAP類型的行鎖 ,會(huì)再次調(diào)用lock_rec_has_expl函數(shù),再次遍歷(也就是多了一次遍歷cell)整個(gè)cell,如同前面提到的一樣(這個(gè)流程參考第四部分)。但是這里并不是做請(qǐng)求行鎖和本事當(dāng)前行鎖的比對(duì),而是通過(guò)這個(gè)函數(shù)去查找到是否當(dāng)前事務(wù)已經(jīng)獲取了一個(gè)LOCK_S類型的鎖,從STRONGER-OR-EQUAL矩陣中,
* S X
* S + -
* X + +
可以看到如果需要檢測(cè)是LOCK_S ,那么只能找到LOCK_S LOCK_MODE的鎖,并且一定是本事務(wù)(這個(gè)流程參考第四部分)。簡(jiǎn)單說(shuō)這里就是為了找到本事務(wù)是否曾經(jīng)獲取了LOCK_S的LOCK_MODE鎖結(jié)構(gòu)。比如例子中TRX1就曾經(jīng)獲取了LOCK_S的鎖,接下來(lái)則標(biāo)記為Conflict::CAN_BYPASS,也就是可以直接執(zhí)行了,而不會(huì)受到TRX2堵塞的影響。
六、回到案例中
好了,有了前面的鋪墊,這里我們簡(jiǎn)單分析一下,這個(gè)流程
- 首先TRX1請(qǐng)求行鎖為L(zhǎng)OCK_REC_NOT_GAP|LOCK_S ,這個(gè)毫無(wú)疑問(wèn)會(huì)成功
- 接著TRX2請(qǐng)求行鎖為L(zhǎng)OCK_REC_NOT_GAP|LOCK_X ,這個(gè)時(shí)候首先是請(qǐng)求行鎖和本事當(dāng)前行鎖的比對(duì),這個(gè)因?yàn)門RX2沒(méi)有獲取過(guò)LOCK_X LOCK_MODE的鎖,因此需要進(jìn)行請(qǐng)求行鎖和其他事務(wù)是否沖突的判斷,而找到肯定就是TRX1持有的鎖,也就是遍歷到的當(dāng)前行鎖,因?yàn)長(zhǎng)OCK_S和LOCK_X 肯定在兼容矩陣不兼容因此TRX2等待,且鎖結(jié)構(gòu)標(biāo)記為L(zhǎng)OCK_WAIT
- 然后TRX1請(qǐng)求行鎖為L(zhǎng)OCK_REC_NOT_GAP|LOCK_X,這個(gè)時(shí)候首先是請(qǐng)求行鎖和本事當(dāng)前行鎖的比對(duì),TRX1獲取過(guò)LOCK_S LOCK_MODE的鎖,但是因?yàn)長(zhǎng)OCK_S相對(duì)較弱,因此LOCK_X不能直接過(guò)去。然后需要進(jìn)行請(qǐng)求行鎖和其他事務(wù)是否沖突的判斷,這個(gè)時(shí)候遍歷到TRX2處于持有的鎖結(jié)構(gòu)時(shí)發(fā)現(xiàn)LOCK_X和LOCK_X并不兼容,也沒(méi)有什么高優(yōu)先級(jí)和GAP之類的判定,因此需要TRX1也需要等待,并且將本次請(qǐng)求的鎖結(jié)構(gòu)標(biāo)記為L(zhǎng)OCK_WAIT,觸發(fā)死鎖。但是到了8029及之后,在標(biāo)記為L(zhǎng)OCK_WAIT之前,會(huì)額外的多一次判斷,也就是找到TRX1是否曾經(jīng)獲取過(guò)LOCK_S,那么TRX1的這個(gè)語(yǔ)句可以直接完成,不需要在等待TRX2的處于LOCK_WAIT的鎖結(jié)構(gòu)了。
其他
關(guān)于類似死鎖的記錄
類似死鎖可以發(fā)生在唯一索引和主鍵上,出現(xiàn)后會(huì)發(fā)現(xiàn)死鎖的TRANSACTION 1中HOLDS THE LOCK(S)和WAITING FOR THIS LOCK TO BE GRANTED 記錄時(shí)完全一樣的,這是因?yàn)檫@個(gè)鎖結(jié)構(gòu)既被堵塞了,同時(shí)也堵塞了其他的記錄。從死鎖檢測(cè)來(lái)看,實(shí)際上通過(guò)info容器和outgoing容器,特別是outgoing容器能夠找到相互等待的事務(wù),然后通過(guò)函數(shù)Deadlock_notifier::notify來(lái)打印出信息
------------------------
LATEST DETECTED DEADLOCK
------------------------
2024-11-06 16:09:26 0x7fff167fd700
*** (1) TRANSACTION:
TRANSACTION 947046, ACTIVE 4 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1200, 1 row lock(s)
MySQL thread id 20, OS thread handle 140736551925504, query id 46774 localhost root statistics
select * from test where id=20 for update
*** (1) HOLDS THE LOCK(S): ----> same
RECORD LOCKS space id 352 page no 4 n bits 72 index PRIMARY of table `t10`.`test` trx id 947046 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 4; hex 80000014; asc ;;
1: len 6; hex 0000000dcb87; asc ;;
2: len 7; hex 810000016e0110; asc n ;;
3: len 1; hex 67; asc g;;
4: len 1; hex 67; asc g;;
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: ----> same
RECORD LOCKS space id 352 page no 4 n bits 72 index PRIMARY of table `t10`.`test` trx id 947046 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 4; hex 80000014; asc ;;
1: len 6; hex 0000000dcb87; asc ;;
2: len 7; hex 810000016e0110; asc n ;;
3: len 1; hex 67; asc g;;
4: len 1; hex 67; asc g;;
*** (2) TRANSACTION:
TRANSACTION 947047, ACTIVE 20 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1200, 2 row lock(s)
MySQL thread id 19, OS thread handle 140733529601792, query id 46775 localhost root statistics
select * from test where id=20 for update
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 352 page no 4 n bits 72 index PRIMARY of table `t10`.`test` trx id 947047 lock mode S locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 4; hex 80000014; asc ;;
1: len 6; hex 0000000dcb87; asc ;;
2: len 7; hex 810000016e0110; asc n ;;
3: len 1; hex 67; asc g;;
4: len 1; hex 67; asc g;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 352 page no 4 n bits 72 index PRIMARY of table `t10`.`test` trx id 947047 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 4; hex 80000014; asc ;;
1: len 6; hex 0000000dcb87; asc ;;
2: len 7; hex 810000016e0110; asc n ;;
3: len 1; hex 67; asc g;;
4: len 1; hex 67; asc g;;
*** WE ROLL BACK TRANSACTION (1)
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000030fabc5 in main(int, char**) at /pxc/mysql-8.0.36/sql/main.cc:25
breakpoint already hit 1 time
4 breakpoint keep y 0x0000000004a84696 in lock_rec_lock_slow(bool, select_mode, ulint, buf_block_t const*, ulint, dict_index_t*, que_thr_t*) at /pxc/mysql-8.0.36/storage/innobase/lock/lock0lock.cc:1823
breakpoint already hit 11 times
5 breakpoint keep y 0x0000000004a80ba7 in operator()(ib_lock_t const*) const at /pxc/mysql-8.0.36/storage/innobase/lock/lock0lock.cc:829
breakpoint already hit 16 times
7 breakpoint keep y 0x0000000004a7fe18 in locksys::rec_lock_check_conflict(trx_t const*, ulint, ib_lock_t const*, bool, locksys::Trx_locks_cache&) at /pxc/mysql-8.0.36/storage/innobase/lock/lock0lock.cc:494
breakpoint already hit 8 times
8 breakpoint keep y 0x0000000004a7fe18 in locksys::rec_lock_check_conflict(trx_t const*, ulint, ib_lock_t const*, bool, locksys::Trx_locks_cache&) at /pxc/mysql-8.0.36/storage/innobase/lock/lock0lock.cc:494
breakpoint already hit 8 times
修改 加入新的數(shù)據(jù)結(jié)構(gòu)
namespace locksys {
/** An object which can be passed to consecutive calls to
rec_lock_has_to_wait(trx, mode, lock, is_supremum, trx_locks_cache) for the same
trx and heap_no (which is implicitly the bit common to all lock objects passed)
which can be used by this function to cache some partial results. */
class Trx_locks_cache {
private:
bool m_computed{false};
bool m_has_s_lock_on_record{false};
#ifdef UNIV_DEBUG
const trx_t *m_cached_trx{};
page_id_t m_cached_page_id{0, 0};
size_t m_cached_heap_no{};
#endif /* UNIV_DEBUG*/
public:
/* Checks if trx has a granted lock which is blocking the waiting_lock.
@param[in] trx The trx object for which we want to know if one of
its granted locks is one of the locks directly
blocking the waiting_lock.
It must not change between invocations of this
method.
@param[in] waiting_lock A waiting record lock. Multiple calls to this method
must query the same heap_no and page_id. Currently
only X and X|REC_NOT_GAP are supported.
@return true iff the trx holds a granted record lock which is one of the
reasons waiting_lock has to wait.
*/
bool has_granted_blocker(const trx_t *trx, const lock_t *waiting_lock);
};
/** Checks if a lock request lock1 has to wait for request lock2. It returns the
same result as @see lock_has_to_wait(lock1, lock2), but in case these are record
locks, it might use lock1_cache object to speed up the computation.
If the same lock1_cache is passed to multiple calls of this method, then lock1
also needs to be the same.
@param[in] lock1 A waiting lock
@param[in] lock2 Another lock;
NOTE that it is assumed that this has a lock bit set
on the same record as in lock1 if the locks are record
locks.
@param[in] lock1_cache An object which can be passed to consecutive calls to
this function for the same lock1 which can be used by
this function to cache some partial results.
@return true if lock1 has to wait for lock2 to be removed */
bool has_to_wait(const lock_t *lock1, const lock_t *lock2,
Trx_locks_cache &lock1_cache);
} // namespace locksys
+namespace locksys {
+/** Checks if a new request for a record lock has to wait for existing request.
+@param[in] trx The trx requesting the new lock
+@param[in] type_mode precise mode of the new lock to set: LOCK_S or
+ LOCK_X, possibly ORed to LOCK_GAP or
+ LOCK_REC_NOT_GAP, LOCK_INSERT_INTENTION
+@param[in] lock2 another record lock;
+ NOTE that it is assumed that this has a lock
+ bit set on the same record as in the new lock
+ we are setting
+@param[in] lock_is_on_supremum true if we are setting the lock on the
+ 'supremum' record of an index page: we know
+ then that the lock request is really for a
+ 'gap' type lock
+@param[in] trx_locks_cache An object which can be passed to consecutive
+ calls to this function for the same trx and
+ heap_no (which is implicitly the bit common to
+ all lock2 objects passed) which can be used by
+ this function to cache some partial results.
+@return true if new lock has to wait for lock2 to be removed */
+static inline bool rec_lock_has_to_wait(const trx_t *trx, ulint type_mode,
+ const lock_t *lock2,
+ bool lock_is_on_supremum,
+ Trx_locks_cache &trx_locks_cache)
namespace locksys
rec_lock_check_conflict
has_to_wait
rec_lock_has_to_wait
lock_rec_has_to_wait 去掉 新增locksys::rec_lock_check_conflict 8036
lock_has_to_wait 更改為下面函數(shù)的封裝
新增 has_to_wait
新增 lock_has_to_wait 封裝函數(shù),調(diào)用使用Trx_locks_cache
新增 rec_lock_has_to_wait
locksys::Trx_locks_cache::has_granted_blocker
lock_rec_other_has_conflicting
1315 = 1024+256+32 + 2 + 1
LOCK_REC_NOT_GAP LOCK_WAIT LOCK_REC
10100100011
10100100000
10000000011
1058:
10000100010
LOCK_REC_NOT_GAP LOCK_REC LOCK_S
LOCK_S 2
LOCK_X 3
LOCK_REC_NOT_GAP 1024
LOCK_REC 32
LOCK_WAIT 256
LOCK_ORDINARY 0
1026
LOCK_REC_NOT_GAP 1024
LOCK_S 2
(gdb) p lock->trx->id
$30 = 530729
(gdb) p ock->is_waiting()
No symbol "ock" in current context.
(gdb) p lock->is_waiting()
$31 = false
(gdb) p lock->type_mode
$32 = 1058
(gdb) p lock->rec_lock
$33 = {page_id = {m_space = 87, m_page_no = 4}, n_bits = 72}
Breakpoint 5, operator() (__closure=0x7fffa06f2a60, lock=0x7fffe000e3e0) at /pxc/mysql-8.0.36/storage/innobase/lock/lock0lock.cc:829
829 return !(lock->is_waiting() ||
2: lock->type_mode = 1315
1: lock->trx->id = 530730
(gdb) p lock->is_waiting()
$45 = true
(gdb) p first->is_waiting()
$48 = true
1315
LOCK_REC_NOT_GAP 1024
LOCK_WAIT 256
LOCK_REC 32
LOCK_X 3
LOCK_REC_NOT_GAP
LOCK_MODE_MASK: 0XF
LOCK_IS = 0
LOCK_IX =1
LOCK_S =2
LOCK_X =3
LOCK_AUTO_INC =4
LOCK_TYPE_MASK:0XF0
LOCK_TABLE:16
LOCK_TABLE:32
屬性:
LOCK_WAIT:256
LOCK_ORDINARY :0
LOCK_GAP:512
LOCK_REC_NOT_GAP:1024
LOCK_INSERT_INTENTION:2048
/* Basic lock modes */
enum lock_mode {
LOCK_IS = 0, /* intention shared */
LOCK_IX, /* intention exclusive */
LOCK_S, /* shared */
LOCK_X, /* exclusive */
LOCK_AUTO_INC, /* locks the auto-inc counter of a table
in an exclusive mode */
LOCK_NONE, /* this is used elsewhere to note consistent read */
LOCK_NUM = LOCK_NONE, /* number of lock modes */
LOCK_NONE_UNSET = 255
};
lock_rec_lock_slow
->lock_rec_has_expl
->lock_rec_has_expl
找到是否有更高級(jí)級(jí)別滿足條件的鎖,如果沒(méi)有則需要進(jìn)行沖突判斷
這里8036來(lái)看,更改對(duì)于處于waitting狀態(tài)鎖的判斷,但是在lock_rec_has_expl
中還是進(jìn)行判斷,如果找到是處于waitting狀態(tài)的依舊需要進(jìn)行沖突判斷
主要是通過(guò)迭代hash 結(jié)構(gòu)并且heap no位設(shè)置了本行鎖的鎖結(jié)構(gòu)進(jìn)行返回,然后
通過(guò)回調(diào)函數(shù)進(jìn)行條件判斷,也就是對(duì)本行現(xiàn)有加鎖的鎖結(jié)構(gòu)的信息和本次加鎖的信息
進(jìn)行匹配包含,我們簡(jiǎn)稱 已鎖定行的鎖和獲取本行的鎖,條件如下
->lock_rec_other_has_conflicting
->locksys::Trx_locks_cache trx_locks_cache{};
建立cache
->Lock_iter::for_each
迭代 lock 為在hash中找到的 本行記錄的鎖,迭代對(duì)本行上鎖的所有每個(gè)鎖結(jié)構(gòu),然后調(diào)用
locksys::rec_lock_check_conflict,確認(rèn)是否沖突
->可以通過(guò)的部分
1、如果當(dāng)前的鎖結(jié)構(gòu)的事物ID和需要獲取鎖的事物ID相同,且兼容 無(wú)沖突
2、如果本事務(wù)是高權(quán),但是當(dāng)前的鎖結(jié)構(gòu)處于等待,切鎖結(jié)構(gòu)中的權(quán)限不是高權(quán)限(SQL線程)
3、如果本行記錄是sup列 或者 獲取的是gaplock 并且不是 插入印象鎖則無(wú)沖突
4、需要獲取的不是插入映像鎖,并且當(dāng)前的鎖結(jié)構(gòu)是gap lock
5、獲取的是gap lock,但是當(dāng)前的鎖結(jié)構(gòu)是 非gap 結(jié)構(gòu),也不會(huì)堵塞
6、如果當(dāng)前的鎖結(jié)構(gòu)是插入映像鎖,則不沖突
7、新增
A.如果不是插入映像鎖
B.當(dāng)前的鎖處于waitting狀態(tài)
C.當(dāng)前的鎖是lock X
D.需要獲取的鎖模式是 lock X
需要額外判定是否存在S鎖的存在
Trx_locks_cache::has_granted_blocker
這里帶入的是本次的事務(wù),而lock2為當(dāng)前鎖結(jié)構(gòu)
->m_has_s_lock_on_record =
lock_rec_has_expl(LOCK_S | LOCK_REC_NOT_GAP, page_id, heap_no, trx)
這里主要檢測(cè)是否有S鎖的存在,這里看起來(lái)是全部重新掃描一次
->迭代
->locksys::rec_lock_check_conflict
->locksys::Trx_locks_cache::has_granted_blocker
->lock_rec_has_expl
(p_implies_q(lock->is_record_not_gap(), is_rec_not_gap) &&
p_implies_q(lock->is_gap(), is_gap)))));
獲取鎖是key lock或next key lock不是gap lock ,需要的鎖是gap lock 不是key lock或next key lock
1 0
1 || !0 && 0||!1 = 0
獲取鎖是key lock或next key lock不是gap lock ,需要的鎖是key lock或next key lock不是gap lock
1 1
1 || !1 && 0||!0 = 1
獲取鎖是gap lock不是key lock或next key lock ,需要的鎖是gap lock不是key lock或next key lock
1 1
0 || !0 && 1|| !1 = 1
獲取鎖是gap lock不是key lock或next key lock ,需要的鎖是key lock或next key lock不是gap lock
0 1
0 || !1 && 1|| !0 = 0
當(dāng)前持有鎖是key lock或next key lock ,需要的鎖是gap lock 0
當(dāng)前持有鎖是key lock或next key lock,需要的鎖是key lock或next key lock 1
當(dāng)前持有鎖是gap lock,需要的鎖是gap lock 1
當(dāng)前持有鎖是gap lock ,需要的鎖是key lock或next key lock 0