01
兼容性矩阵
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_INLINEiboollock_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 flagdo not need to wait for anything. This is becausedifferent users can have conflicting lock typeson gaps. */return(FALSE);}if (!(type_mode & LOCK_INSERT_INTENTION)&& lock_rec_get_gap(lock2)) {/* Record lock (LOCK_ORDINARY or LOCK_REC_NOT_GAPdoes 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 fora 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_INLINEulintlock_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 删除。




