MySQL 核心模块揭秘 | 22 期 | 行锁 (2) 慢速加锁

目录

  • 1. 加过锁了吗?

  • 2. 需要等待吗?

  • 3. 先找个复用的行锁结构

  • 4. 没找到就申请一个新的

  • 5. 总结

正文

1. 加过锁了吗?

快速加锁逻辑主打简单、快速,它只能处理简单的情况,即通过简单的判断就能确定本次加锁操作不会被阻塞。

对于复杂一点的情况,就需要慢速加锁逻辑来处理了。

关于什么是复杂的情况,可以看前面介绍的慢速加锁条件,命中任何一个慢速加锁条件的,就是复杂的情况。

为了方便介绍和理解,本文中,我们把本次加锁的事务称为 T1,本次加锁的记录称为 R1。

慢速加锁逻辑主打全方位无死角,可以处理更复杂的情况。它会判断事务 T1 是否对记录 R1 加过相同或者更高级别的行锁。如果是,本次就不需要重复加锁了。

既然主打全方位无死角,那就不能漏掉任何一个行锁结构。慢速加锁逻辑会遍历记录 R1 所属数据页对应的所有行锁结构。

遍历过程需要先找到记录 R1 所属数据页在 rec_hash
的数组中对应的行锁结构链表,步骤如下:

  • 根据记录 R1 所属数据页的页号、表空间 ID 计算得到哈希值。
  • 上一步的哈希值,对 rec_hash 的数组单元数量取模(哈希值 % 数组单元数量),得到 rec_hash 的数组下标。
  • 根据上一步的数组下标,得到对应的行锁结构链表。

如果事务 T1 没有对记录 R1 加过锁,找到的行锁结构链表有可能为空,也有可能不为空。

因为其它事务对记录 R1 加锁,锁结构会加入这个行锁结构链表。包含 T1 在内的任何事务对其它记录加锁,锁结构也可能放入这个行锁结构链表。

如果事务 T1 对记录 R1 加过锁,找到的行锁结构链表一定不为空。

如果找到的行锁结构链表不为空,就可以开始遍历了。遍历时,从链表头部开始,每次取出一个行锁结构。

因为行锁结构链表是多个行锁结构通过各自的 hash
属性串连成的链表,我们把遍历过程中每次取出的一个行锁结构称为 hash 行锁结构。

对于每个 hash 行锁结构,都会判断两个条件:

  • hash 行锁结构的 page_id
    属性中保存的数据页的页号、表空间 ID,和记录 R1 所属数据页的页号、表空间 ID 相同。
  • hash 行锁结构的 bitmap 中,对应记录 R1 的位的值为 1。

如果以上两个条件有一个不成立,说明这个 hash 行锁结构和记录 R1 没有关系,直接忽略这个行锁结构。

如果以上两个条件都成立,说明这个 hash 行锁结构有可能和记录 R1 有关系,需要进一步判断。

首先,看看这个 hash 行锁结构是否处于锁等待状态,如果是,说明这个行锁结构对应的行锁不可能满足本次加锁要求,直接忽略。

然后,看看这个 hash 行锁结构是否是事物 T1 创建的,如果不是,说明这个行锁结构对应的行锁也不可能满足本次加锁要求,直接忽略。

经过前面的一系列判断,如果这个 hash 行锁结构还没有被忽略,那么,它对应的行锁就有可能满足本次加锁要求。

接下来,需要判断它的锁模式、精确模式和本次加锁的锁模式、精确模式的强弱关系,判断过程分为两部分。

第一部分,判断 hash 行锁结构的锁模式,和本次加锁的锁模式的强弱关系。

和表锁一样,判断行锁的锁模式的强弱关系,也需要借助一个强弱关系图。

MySQL 核心模块揭秘 | 22 期 | 行锁 (2) 慢速加锁-1

具体的判断逻辑如下:

  • 根据 hash 行锁结构的锁模式,找到上图中对应的行。
  • 根据本次加锁的锁模式,找到上一步的行中对应的列。
  • 确定了行和列之后,就有了表示锁模式强弱关系的结果。