暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

InnoDB行锁兼容性矩阵

MySQLLabs 2019-09-15
3235

                


01

兼容性矩阵 


事务在对InnoDB中的数据进行加锁操作时,需要判断是否存在与之冲突的锁,这个过程是通过函数lock_rec_other_has_conflicting来实现的,伪代码如下:
    lock_rec_other_has_conflicting(unsigned long, buf_block_t const*, unsigned long, trx_t const*)
    {
    /*检测要加锁的索引是否为supremum*/
    bool is_supremum = (heap_no == PAGE_HEAP_NO_SUPREMUM);
    /*从锁hash表中去查看对应到此索引记录的锁*/
    for (lock = lock_rec_get_first(lock_sys->rec_hash, block, heap_no);lock != NULL;lock = lock_rec_get_next_const(heap_no, lock))
    {
    /*检测检索到的锁结构与此事务要添加的锁是否冲突*/
    if (lock_rec_has_to_wait(trx, mode, lock, is_supremum))
    {
    return(lock);
    }
    }
    }


    通过函数lock_rec_has_to_wait去判断同一个索引记录上的锁是否冲突,实现如下:

      UNIV_INLINE
      ibool
      lock_rec_has_to_wait(
      /*=================*/
      const trx_t* trx, /*当前事务结构*/
      ulint type_mode,/*将要添加的锁的精确模式,不仅包含了索引的类型,还有具体的范围,比如LOCK_S或者LOCK_X,以及LOCK_GAP或者是LOCK_REC_NOT_GAP,LOCK_INSERT_INTENTION*/
      const lock_t* lock2, /*上层函数中寻找到的锁结构*/
      bool lock_is_on_supremum)/*此事务当前要加锁的索引记录是否为supremum*/
      {
      /*首先要判断这两个锁对象是否属于同一个事务,如果属于同一个事务,则不冲突;否则需要进行锁兼容性矩阵的初步检测*/
      if (trx != lock2->trx
      && !lock_mode_compatible(static_cast<lock_mode>(
      LOCK_MODE_MASK & type_mode),
      lock_get_mode(lock2))) {
      /*在锁兼容性矩阵显示不冲突的情况下,则不冲突,否则还需要进行如下判断*/
      if ((lock_is_on_supremum || (type_mode & LOCK_GAP))
      && !(type_mode & LOCK_INSERT_INTENTION)) {


      /* Gap type locks without LOCK_INSERT_INTENTION flag
      do not need to wait for anything. This is because
      different users can have conflicting lock types
      on gaps. */


      return(FALSE);
      }


      if (!(type_mode & LOCK_INSERT_INTENTION)
      && lock_rec_get_gap(lock2)) {


      /* Record lock (LOCK_ORDINARY or LOCK_REC_NOT_GAP
      does not need to wait for a gap type lock */


      return(FALSE);
      }


      if ((type_mode & LOCK_GAP)
      && lock_rec_get_rec_not_gap(lock2)) {


      /* Lock on gap does not need to wait for
      a LOCK_REC_NOT_GAP type lock */


      return(FALSE);
      }
      if (lock_rec_get_insert_intention(lock2)) {
      return(FALSE);
      }


      return(TRUE);
      }


      return(FALSE);
      }


      锁兼容性矩阵其实是一个二维数组,通过函数lock_mode_compatible传入待对比的锁的精确模式,可以直接得到结果,非常高效。函数实现如下:

        UNIV_INLINE
        ulint
        lock_mode_compatible(
        /*=================*/
        enum lock_mode mode1, *!< in: lock mode */
        enum lock_mode mode2) *!< in: lock mode */
        {
        return(lock_compatibility_matrix[mode1][mode2]);
        }


        代表锁兼容性矩阵的二维数组如下:

          static const byte lock_compatibility_matrix[5][5] = {
          /** IS IX S X AI */
          /* IS */ { TRUE, TRUE, TRUE, FALSE, TRUE},
          /* IX */ { TRUE, TRUE, FALSE, FALSE, TRUE},
          /* S */ { TRUE, FALSE, TRUE, FALSE, FALSE},
          /* X */ { FALSE, FALSE, FALSE, FALSE, FALSE},
          /* AI */ { TRUE, TRUE, FALSE, FALSE, FALSE}
          };


          这也是在别的资料中经常看到的InnoDB锁兼容性矩阵的由来,如下图所示:

          但是两锁是否兼容,不仅仅取决于lock_mode_compatible返回的结果,在兼容性矩阵返回TRUE的情况下,是一定兼容的,但是在返回FALSE时,并不一定不兼容,下面来介绍这些特殊情况。


          02

          特例一 


          如果此事务新创建的锁的索引记录为supremum或者此事务新创建的锁模式为间隙锁,并且锁的模式不为插入意向锁,则两锁兼容。其中这里就包含了一种我们比较熟悉的情况,就是常说的间隙锁与间隙锁不冲突(无论是共享锁还是排他锁),如下图所示:

          图中展示了两个事务同时对表t1中的主键索引记录10添加排他间隙锁的情况,可以看到是不冲突的。但是如果第二个事务带有插入意向属性,则不兼容,进入锁等待,如下图所示:


          03

          特例二 


          如果此事务新创建的锁的模式不为插入意向锁,并且已存在的锁模式为间隙锁,则两锁兼容。如下图所示:

          左侧事务操作对t1表中的主键索引记录15添加排他间隙锁的,右侧事务对t1表中的主键索引记录15添加记录锁,两锁兼容。


          04

          特例三 


          如果此事务新创建的锁的模式是间隙锁,并且已存在的锁的模式为记录锁(只锁记录,不锁间隙),则两锁兼容。

          与特殊情况二类似,只不过加锁顺序颠倒,两个事务分别先添加索引记录的记录锁,再添加索引记录的gap锁,两锁也是兼容的。


          05

          特例4


               如果已经存在的锁的模式为插入意向锁,则两锁兼容。如下图所示:

          session-1中的事务通过操作-2获取对主键索引记录id为10(参照上文中的表中数据)的gap锁,随后session-2中的操作3会添加对主键索引记录id为10的gap锁,带有插入意向属性,并且会被阻塞,session-1继续操作4的插入操作,会添加对主键索引记录id为10的插入意向锁,此时与session-2中的锁是不冲突的。

          通过这些特殊情况可以说明,严格来讲,排他锁和排他锁或者共享锁不兼容这句话是不完全正确的。对加锁过程和锁冲突的理解可以方便我们进行线上的死锁排查,以及进行锁的优化。这些特殊情况在后续的版本中可能会继续增加。

           

          注意:如上所述全部基于REPEATABLE-READ事务隔离级别。







          本文分享自微信公众号 - MySQLLabs,如有侵权,请联系 service001@enmotech.com 删除。
          最后修改时间:2020-01-14 09:53:30
          文章转载自MySQLLabs,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论