美文网首页
一个古老的死锁BUG终于修复了

一个古老的死锁BUG终于修复了

作者: 重庆八怪 | 来源:发表于2024-11-05 18:31 被阅读0次
  • 因为这里描述全部是Innodb的行锁,为了方便描述,这里将语句需要的锁资源描述为请求行锁,而已经获取或者等待的行锁描述为当前行锁
  • 其次锁结构对于行锁来讲和page绑定,一个锁结构可以用多个bit位表示一个page的多行记录锁,每个bit代表一行记录。但是这需要满足一定的条件,下面描述。
  • 这里讨论也是慢速加锁(lock_rec_lock_slow)的情况,不考虑快速加锁。

一、 问题所在和BUG修复

这个问题大概如下,也是最近看到很多朋友在讨论,也遇到过。我们使用8023和8036分别测试,表结构如下,隔离级别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死锁检测,8036继续堵塞

这个问题最终被计入了BUG,看起来这个BUG由来已久,如下以下

  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

修复版本为8029,修复点很多,这里重点关注下为什么8036不会造成死锁,其他的暂时不做分析。还需要注意在BUG 101695中可以看出这个问题并不一定出现在这种构造的场景中,在并发的update,且包含unique索引,做唯一性检查的是否也有可能出现,但是我暂时没有构造出来必现的场景,如果需要模拟可以参考这个BUG中的描述。这种死锁在死锁日志有2个特点,

  • 某个TRANSACTION 的HOLDS THE LOCK(S)和WAITING FOR THIS LOCK TO BE GRANTED 记录是完全一样的,这是因为这个锁结构既被堵塞了,同时也堵塞了其他锁结构。这种环形等待通过一个叫做outgoing的容器很容易检测出来,这个容器大概如下,
outgoing容器:
outgoing[0] = 3(info容器下标)    outgoing[0]   [A] wait [D]  D为堵塞者
outgoing[1] = 0(info容器下标)    outgoing[1]   [B] wait [A]  A为堵塞者
outgoing[2] = 0(info容器下标)    outgoing[2]   [C] wait [A]  A为堵塞者
outgoing[3] = 0(info容器下标)    outgoing[3]   [D] wait [A]  A为堵塞者
outgoing[4] = 0(info容器下标)    outgoing[4]   [F] wait [A]  A为堵塞者
       A
| \    |\    |\
|      |     |/   
B      C     D
$60 = std::vector of length 4, capacity 4 = {3, 0, 0, 0}

然后根据环形堵塞的两个事务获取其堵塞和被堵塞的锁结构就可以了。

  • 同时也包含了S lock,在RC模式下通常就是唯一性检查或 for share访问数据。

二、行锁的type_mode

innodb 行锁的type_mode由很多位表示,出现在lock_t(锁结构)的type_mode主要定义有如下,

  • LOCK_MODE_MASK: 0XF(低4位),本文描述为LOCK_MODE
    LOCK_IS : 0
    LOCK_IX :1
    LOCK_S :2
    LOCK_X :3
    LOCK_AUTO_INC :4

  • LOCK_TYPE_MASK:0XF0,本文描述为LOCK_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因为通常都是讨论行锁,因此都是LOCK_REC,在其他属性中包含了是否事gap lock/next key lock/key lock等,也包含了是否处于等待。比如一个type_mode为1058的锁实际上就是,
LOCK_REC_NOT_GAP|LOCK_S 且是LOCK_REC(记录锁)

三、关于记录锁的hash结构

当每一个需要的请求行锁都需要放到一个hash结构中,不管是否获取成功,这个hash结构就是通过通过加锁记录所在的space id和page no进行hash计算的(m_fold(lock_rec_fold(page_id)),也就是说同一个表的同一个page no上的不同锁结构会放到一个cell种,虽然在事务中也会存在本事务持有的全表锁结构,但是如果需要跨事务查找锁结构的时候就需要用到这个hash结构,比如锁冲突检测。当然这里要分2种情况条论,

  • 如果请求行锁需要等待,则新建锁结构,放到cell链表的末尾,参考函数lock_rec_insert_to_waiting
  • 如果请求行锁不需要等待,获取成功,那么需要判断是否有现有的锁结构能够容下本次获取的行锁,满足条件:
    A.事务是同一个
    B.LOCK_MODE/LOCK_TYPE/其他属性 均相同
    C.当前锁结构的bit位能够容下

就会合并到一个锁结构中,当然没有满足条件的锁结构,那么就需要新建了,然后放到cell链表的头部,参考函数(lock_rec_add_to_queue)

这个hash 结构我们可以简单的描述如下。

      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)

四、请求行锁和本事务当前行锁的比对

这个判断主要是调用lock_rec_has_expl函数进行一次判断,主要判断是否本trx是否已经获取了跟关于本行锁更高级或者相等的LOCK_MODE,这样就可以直接获取成功了,不用做什么了。
而这个判断主要遍历刚才说的hash结构的cell,每一个锁结构(lock_t),先判定是否本行加锁的结构,然后通过一个叫做STRONGER-OR-EQUAL的矩阵(注意不是兼容矩阵)进行判断,主要的判定流程大概有如下,如果找到一个符合条件的锁结构就返回,

  • 1:当前行锁的记录必须和请求行锁的记录相同,也就是包含了同一行记录,这个是在迭代的条件中判断的
  • 2:当前行锁的事务ID和请求行锁的事务ID相同
  • 3:确认不是插入印象锁
  • 4:找到的锁没有处于等待,这个条件8036有变动,但是整体逻辑看起来没变
  • 5:STRONGER-OR-EQUAL 矩阵判断 lock S / lock X 是否兼容,下面是这个矩阵(注意不是兼容矩阵)
/* STRONGER-OR-EQUAL RELATION (mode1=row, mode2=column)
 *    IS IX S  X  AI
 * IS +  -  -  -  -
 * IX +  +  -  -  -
 * S  +  -  +  -  -
 * X  +  +  +  +  +
 * AI -  -  -  -  +
 * See lock_mode_stronger_or_eq().

这里我们关注S和X就可以了,后面还会用到,

 *    S  X
 * S  +  - 
 * X  +  + 

可以看到如果是已经获取了S,那么本次如果获取S则是TRUE,如果是X则是FALSE,如果已经获取了X,那么本次如果获取S则是TRUE,如果是X则也是TRUE。

  • 6: 是sup伪列 或者 (条件X),这里条件X比较复杂简单推了一下如下,
    A:当前行锁是key lock或next key lock不是gap lock ,请求行锁是gap lock 不是key lock或next key lock ,返回false (1 || !0 && 0||!1 = 0)
    B:当前行锁是key lock或next key lock不是gap lock ,请求行锁是key lock或next key lock不是gap lock,返回true (1 || !1 && 0||!0 = 1)
    C:当前行锁是gap lock不是key lock或next key lock ,请求行锁是gap lock不是key lock或next key lock,返回true (0 || !0 && 1|| !1 = 1)
    D:当前行锁是gap lock不是key lock或next key lock ,请求行锁是key lock或next key lock不是gap lock,返回false (0||!1 && 1|| !0 =0)

五、请求行锁和其他事务是否冲突

这里主要判定是请求行锁和其他事务的行锁是否有冲突,如果有冲突就需要等待了,这里同样和上面一样是通过变量hash结构的cell来进行的,也都需要是包含本记录的锁结构才会判断,同样是在所有的锁结构中找到一个有冲突的就可以了,否则就是没有冲突。这里主要使用的是兼容矩阵进行判断,这个过程在lock_rec_other_has_conflicting中,其中8036的判定的函数为 locksys::rec_lock_check_conflict,其中包含如下条件,

    1. 当前行锁的记录必须和请求行锁的记录相同,也就是包含了同一行记录,这个是在迭代的条件中判断的
    1. 如果当前行锁的锁结构的事物ID和请求行锁的事物ID相同 或者 请求行锁锁结构的LOCK_MODE和当前行锁的锁结构的 LOCK_MODE 无冲突,这里判断是通过兼容性矩阵进行的,也是比较简单,兼容性矩阵如下,
/* LOCK COMPATIBILITY MATRIX
    IS IX S  X  AI
 IS +    +  +  -  +
 IX +    +  -  -  +
 S  +    -  +  -  -
 X  -    -  -  -  -
 AI +    +  -  -  -
 *

这里重点关注S 和X LOCK_MODE。

    1. 如果本事务是高权,但是当前行锁的锁结构处于等待且当前行锁的持有事务不是高权限(SQL线程)
    1. 如果本行记录是sup列 或者 请求行锁是gap lock, 并且不是 插入印象锁
    1. 请求行锁不是插入映像锁且当前行锁结构 gap lock
    1. 请求行锁是gap lock,但是当前行锁是 非gap lock
    1. 如果当前行锁是插入映像锁

都判定为没有冲突,这里我们可以理解主要是第2点进行了LOCK_MODE的判断,而第3点则是保证主从中的SQL线程获取的锁结构有更高的优先级,但是SQL线程多个worker之间由于都具有高优先级则回归到普通锁结构的判断规则。其他点都是比较特殊的情况,作为补充,主要是对gap lock的补充。
而到了本BUG修复后,比如8.0.36这里多了一个判断条件也就是,如果检测通过也可以不堵塞,这也是这个例子为什么8.0.36不会触发死锁的根本原因。也就是这个条件导致了前面案例中,级别TRX2 由于获取LOCK_MODE X 处于LOCK_WAIT,而TRX1再次获取本行记录的LOCK_MODE X的时候还能够获取到,而不会被TRX2堵塞。这个条件有如下一些先决条件,

  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. 当前的锁处于waitting状态      
    ut_ad(!(type_mode & LOCK_GAP));                                            // C. 当前的锁是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)) { //这里带入的是本次的事务,而lock2为当前锁结构 
      return Conflict::CAN_BYPASS;
    }

主要就是locksys::Trx_locks_cache::has_granted_blocker,这个函数带入了本次请求行锁的事务和当前行锁的锁结构。先决条件中有当前行锁处于waitting状态的条件,也就像例子中处于等待状态的TRX2的那个行锁结构就是这样的。
接下来这个函数通过构造一个LOCK_S | LOCK_REC_NOT_GAP类型的行锁 ,会再次调用lock_rec_has_expl函数,再次遍历(也就是多了一次遍历cell)整个cell,如同前面提到的一样(这个流程参考第四部分)。但是这里并不是做请求行锁和本事当前行锁的比对,而是通过这个函数去查找到是否当前事务已经获取了一个LOCK_S类型的锁,从STRONGER-OR-EQUAL矩阵中,

 *    S  X
 * S  +  - 
 * X  +  + 

可以看到如果需要检测是LOCK_S ,那么只能找到LOCK_S LOCK_MODE的锁,并且一定是本事务(这个流程参考第四部分)。简单说这里就是为了找到本事务是否曾经获取了LOCK_S的LOCK_MODE锁结构。比如例子中TRX1就曾经获取了LOCK_S的锁,接下来则标记为Conflict::CAN_BYPASS,也就是可以直接执行了,而不会受到TRX2堵塞的影响。

六、回到案例中

好了,有了前面的铺垫,这里我们简单分析一下,这个流程

  • 首先TRX1请求行锁为LOCK_REC_NOT_GAP|LOCK_S ,这个毫无疑问会成功
  • 接着TRX2请求行锁为LOCK_REC_NOT_GAP|LOCK_X ,这个时候首先是请求行锁和本事当前行锁的比对,这个因为TRX2没有获取过LOCK_X LOCK_MODE的锁,因此需要进行请求行锁和其他事务是否冲突的判断,而找到肯定就是TRX1持有的锁,也就是遍历到的当前行锁,因为LOCK_S和LOCK_X 肯定在兼容矩阵不兼容因此TRX2等待,且锁结构标记为LOCK_WAIT
  • 然后TRX1请求行锁为LOCK_REC_NOT_GAP|LOCK_X,这个时候首先是请求行锁和本事当前行锁的比对,TRX1获取过LOCK_S LOCK_MODE的锁,但是因为LOCK_S相对较弱,因此LOCK_X不能直接过去。然后需要进行请求行锁和其他事务是否冲突的判断,这个时候遍历到TRX2处于持有的锁结构时发现LOCK_X和LOCK_X并不兼容,也没有什么高优先级和GAP之类的判定,因此需要TRX1也需要等待,并且将本次请求的锁结构标记为LOCK_WAIT,触发死锁。但是到了8029及之后,在标记为LOCK_WAIT之前,会额外的多一次判断,也就是找到TRX1是否曾经获取过LOCK_S,那么TRX1的这个语句可以直接完成,不需要在等待TRX2的处于LOCK_WAIT的锁结构了。

其他

关于类似死锁的记录
类似死锁可以发生在唯一索引和主键上,出现后会发现死锁的TRANSACTION 1中HOLDS THE LOCK(S)和WAITING FOR THIS LOCK TO BE GRANTED 记录时完全一样的,这是因为这个锁结构既被堵塞了,同时也堵塞了其他的记录。从死锁检测来看,实际上通过info容器和outgoing容器,特别是outgoing容器能够找到相互等待的事务,然后通过函数Deadlock_notifier::notify来打印出信息


------------------------
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


修改 加入新的数据结构

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         更改为下面函数的封装
新增 has_to_wait
新增 lock_has_to_wait  封装函数,调用使用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
        找到是否有更高级级别满足条件的锁,如果没有则需要进行冲突判断
        这里8036来看,更改对于处于waitting状态锁的判断,但是在lock_rec_has_expl
        中还是进行判断,如果找到是处于waitting状态的依旧需要进行冲突判断
        
        主要是通过迭代hash 结构并且heap no位设置了本行锁的锁结构进行返回,然后
        通过回调函数进行条件判断,也就是对本行现有加锁的锁结构的信息和本次加锁的信息
        进行匹配包含,我们简称 已锁定行的锁和获取本行的锁,条件如下
        
  ->lock_rec_other_has_conflicting
       ->locksys::Trx_locks_cache trx_locks_cache{};
          建立cache
          
       ->Lock_iter::for_each
          迭代 lock 为在hash中找到的 本行记录的锁,迭代对本行上锁的所有每个锁结构,然后调用
          locksys::rec_lock_check_conflict,确认是否冲突
          ->可以通过的部分
            1、如果当前的锁结构的事物ID和需要获取锁的事物ID相同,且兼容 无冲突
            2、如果本事务是高权,但是当前的锁结构处于等待,切锁结构中的权限不是高权限(SQL线程)
            3、如果本行记录是sup列 或者 获取的是gaplock 并且不是 插入印象锁则无冲突
            4、需要获取的不是插入映像锁,并且当前的锁结构是gap lock
            5、获取的是gap lock,但是当前的锁结构是 非gap 结构,也不会堵塞
            6、如果当前的锁结构是插入映像锁,则不冲突
            7、新增
                  A.如果不是插入映像锁
                  B.当前的锁处于waitting状态  
                  C.当前的锁是lock X
                  D.需要获取的锁模式是 lock X
                  需要额外判定是否存在S锁的存在
                  Trx_locks_cache::has_granted_blocker
                  这里带入的是本次的事务,而lock2为当前锁结构 
                  ->m_has_s_lock_on_record =
                     lock_rec_has_expl(LOCK_S | LOCK_REC_NOT_GAP, page_id, heap_no, trx)
                     这里主要检测是否有S锁的存在,这里看起来是全部重新扫描一次
            
       
       ->迭代
         ->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
 


当前持有锁是key lock或next key lock ,需要的锁是gap lock     0

当前持有锁是key lock或next key lock,需要的锁是key lock或next key lock  1

当前持有锁是gap lock,需要的锁是gap lock  1

当前持有锁是gap lock ,需要的锁是key lock或next key lock 0

相关文章

  • iOS开发中常见bug!(内附解答方法)

    序言 你是否曾经修复了一个 bug ,随后又发现了一个跟刚修复 bug 有关的 bug ,又或是修复 bug 的方...

  • 为什么修复每个 bug 后都要问这 3 个问题?

    你是否曾经修复了一个 bug ,随后又发现了一个跟刚修复 bug 有关的 bug ,又或是修复 bug 的方式引起...

  • 修复每个bug后都要问这4个问题

    你是否曾经修复了一个bug,随后又发现了一个刚修复bug有关的bug,又或是修复bug的方式引起了另一个bug?又...

  • 6岁产品汪 告诉你有些bug为啥“改”不了

    与产品如影随形的是bug,与产品相依为伴的也是bug,bug无处不在,修复一个bug,冒出无数bug,产品崩溃了,...

  • 古老的偶现bug终于被...

    这周遇到的问题值的总结记录下,这次记录三个问题。线上问题最近有些多哈,且之前出现的次数很少。 其一是线上项目再次出...

  • Android热修复原理解析

    概述 热修复即”打补丁“,当一个app上线后,如果发现重大的bug,需要紧急修复。常规的做法是修复bug,然后重新...

  • Android—常用热修复框架

    前言 热修复即<打补丁>,当一个app上线后,如果发现重大的bug,需要紧急修复。常规的做法是修复bug,然后重新...

  • 热修复之Tinker

    热修复是什么 在热修复出现之前,一个已经上线的app中如果出现了bug,即使是一个非常小的bug,不及时更新的话有...

  • Android Tinker

    什么是热修复 在热修复出现之前,一个已经上线的app中如果出现了bug,即使是一个非常小的bug,不及时更新的话有...

  • P3计划第二天- 修了修bug

    完成情况 今天都圆满完成,这两天一直在debug,修复了很多的bug。今天又花了两个小时把发现的一个bug修复了。...

网友评论

      本文标题:一个古老的死锁BUG终于修复了

      本文链接:https://www.haomeiwen.com/subject/xejodjtx.html