一個(gè)古老的死鎖BUG終于修復(fù)了

  • 因?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)已久,如下以下

  1. https://bugs.mysql.com/bug.php?id=105655
  2. https://bugs.mysql.com/bug.php?id=101695
  3. 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 :4

  • LOCK_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,其中包含如下條件,

    1. 當(dāng)前行鎖的記錄必須和請(qǐng)求行鎖的記錄相同,也就是包含了同一行記錄,這個(gè)是在迭代的條件中判斷的
    1. 如果當(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。

    1. 如果本事務(wù)是高權(quán),但是當(dāng)前行鎖的鎖結(jié)構(gòu)處于等待且當(dāng)前行鎖的持有事務(wù)不是高權(quán)限(SQL線程)
    1. 如果本行記錄是sup列 或者 請(qǐng)求行鎖是gap lock, 并且不是 插入印象鎖
    1. 請(qǐng)求行鎖不是插入映像鎖且當(dāng)前行鎖結(jié)構(gòu) gap lock
    1. 請(qǐng)求行鎖是gap lock,但是當(dāng)前行鎖是 非gap lock
    1. 如果當(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。