MySQL 核心模块揭秘 | 21 期 | 行锁 (1) 快速加锁
目录
1. 两种加锁逻辑
2. 先拿个令牌
3. 再获取行锁结构
4. 快速加锁之一
5. 快速加锁之二
6. 慢速加锁条件有哪些?
7. 总结
正文
1. 两种加锁逻辑
更新、删除记录都需要加行锁,读取、插入记录有时候也需要加行锁,这意味着加行锁是个比较频繁的操作。
对于频繁的操作,为了性能着想,优化是件必须要做的事。
为此,InnoDB 把加行锁操作分为两种逻辑:快速加锁、慢速加锁。
每次加行锁,只要满足快速加锁条件,就会走快速加锁逻辑,提升加锁效率,不满足时,才走慢速加锁逻辑。
2. 先拿个令牌
前面我们介绍过锁模块结构,它有个 rec_hash
属性,是个哈希表,用于管理 InnoDB 中所有的行锁结构。
学习过哈希表的读者应该有所了解,哈希表通常使用数组作为底层数据结构,来管理加入其中的对象。
既然 rec_hash 是个哈希表,它自然也用数组来管理行锁结构了。
数组有多少个单元,由 InnoDB 的 buffer pool 大小和一个数据页大小共同决定。计算公式如下:
// srv_buf_pool_size 为 buffer pool 大小<br>// UNIV_PAGE_SIZE 为一个数据页大小<br>// 两者的单位都是字节<br>5 * (srv_buf_pool_size / UNIV_PAGE_SIZE)<br>
5. 快速加锁之二
前面获取加锁记录所属数据页的第一个行锁结构,如果获取到了,情况就复杂了一点点,因为既有可能走快速加锁逻辑,也有可能走慢速加锁逻辑。
如果这个锁结构命中了慢速加锁条件中的任何一个,就只能乖乖的走慢速加锁逻辑了,否则,也可以走快速加锁逻辑。
至于慢速加锁条件有哪些,先按下不表,稍后再说。
我们先来介绍第二种快速加锁逻辑,因为获取到了加锁记录所属数据页的第一个行锁结构,也就有了可以复用的行锁结构,本次加行锁不用再申请新的行锁结构。
因为不需要申请新的行锁结构,这种快速加锁逻辑比较简单,分为两种情况。
情况 1,如果获取到的行锁结构中,bitmap 内存区域对应本次加锁记录的位已经被设置为 1,说明当前事务已经对这条记录加了锁,本次加行锁的流程到此结束。
情况 2,如果获取到的行锁结构中,bitmap 内存区域对应本次加锁记录的位还是 0,那就把这个位设置为 1,本次加行锁的流程也就结束了。
6. 慢速加锁条件有哪些?
介绍第二种快速加锁逻辑时,我们提到了慢速加锁条件,现在是时候聊聊它们了。慢速加锁条件决定了走慢速加锁逻辑,还是走第二种快速加锁逻辑。
慢速加锁条件有四个,只要满足其中一个,就得乖乖的走慢速加锁逻辑。
条件 1,加锁记录所属的数据页,有两个或多个锁结构。这些锁结构有可能由一个或多个事务创建,这些事务可能包含,也可能不包含本次加锁的事务。
条件 2,前面获取到了加锁记录所属数据页的第一个行锁结构,但是这个锁结构不是当前事务创建的。
条件 3,获取到的第一个行锁结构的锁模式,和本次要加的行锁的锁模式不同。
条件 4,获取到的第一个行锁结构的 bitmap 内存区域空间不够,没有本次加锁记录对应的位,不能用于本次加锁操作。
7. 总结
快速条件的主要流程如下:
获取加锁记录所属数据页对应的第一个行锁结构(不管这个行锁结构是当前事务创建的,还是其它事务创建的)。
如果上一步没有获取到行锁结构,可以走第一种快速加锁逻辑。
首先,需要申请一个新的行锁结构,并初始化行锁结构的各属性、bitmap 内存区域。
然后,把行锁结构加入 rec_hash 中某个数组下标对应的行锁结构链表、当前事务对象的 trx_locks 链表。如果上一步获取到了行锁结构,并且没有命中任何一个慢速加锁条件,可以走第二种快速加锁逻辑。
这种加锁逻辑很简单,只需要把行锁结构的 bitmap 内存区域中,本次加锁记录对应的位设置为 1(如果之前已经设置为 1,本次不需要重复操作)。