• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

MySQL InnoDB插入意向锁

MySQL InnoDB 彭东稳 6年前 (2019-02-19) 25931次浏览 已收录 0个评论

INSERT INTENTION LOCK,翻译为插入意向锁,锁类型为 LOCK_INSERT_INTENTION,其实准确来说应该是 INSERT INTENTION GAP LOCK,属于 GAP LOCK 子类。这个锁类型在老版本的 InnoDB 中并不存在,后来是为了优化插入性能而设计的。

INSERT INTENTION LOCK 在官方文档中的说明如下:

在 INSERT 操作插入成功后,会在新插入的行上设置一个独占索引记录锁(index record lock)。但在插入行之前,INSERT 操作会首先在索引记录之间的间隙上设置 INSERT INTENTION LOCK,该锁的范围是(插入值,向下的一个索引值)。有 shard 或 exclusive 两种模式,但两种模式没有任何区别,二者等价。

在插入行之前,一种叫做插入意向间隙锁(insert intention gap lock)的间隙锁被设置。这种锁发出了即将要插入数据意图的信号,如果多个事务在同一索引间隙中插入的位置不一样,就不需要互相等待。假设有值为 4 和 7 的索引记录,有两个独立事务尝试插入值 5 和 6,在获得插入行上的独占索引记录锁之前使用插入意向锁(insert intention lock)锁定 4 和 7 之间的间隙;于是,一个事务的插入意向锁定 但不会相互阻塞,因为行不冲突。

文档的说明依然有些歧义,InnoDB 在插入记录时,这把锁并非每次插入都需要加的。另外,在 RC 事务隔离级别下,由于插入大部分是不需要等待的,所以这把锁大部分时候也是不存在的。只有当发生锁等待时,即插入的这条记录下一条记录(next_rec)有锁,并且带有 GAP 属性,则这时需要对 next_rec 再加一个插入意向锁。由于插入意向锁和 Gap Lock 不兼容,因此需要等待。反过来说就是申请插入意向锁时,会检查插入记录位置的下一条记录上是否持有锁,并且带有 GAP 属性,如果有,则判断是否与插入意向锁冲突。

例如,如果事务 A 插入记录且未提交,这时事务 B 尝试对这条记录加锁,事务 B 会先去判断记录上保存的事务 id 是否活跃,如果活跃的话,那么就帮助事务 A 去建立一个锁对象,然后自身进入等待事务 A 状态,这就是所谓的隐式锁转换为显式锁。

如果 gap lock 或 next-key lock 与 insert intention lock 的范围重叠了,则 gap lock 或 next-key lock 会阻塞 insert intention lock。隔离级别为 RR 时正是利用此特性来解决 phantom row 问题;尽管 insert intention lock 也是一种特殊的 gap lock,但它和普通的 gap lock 不同,insert intention lock 相互不会阻塞,这极大的提高了插入时的并发性。总结如下:

1. gap lock 会阻塞 insert intention lock。事实上,gap lock 的存在只是为了阻塞 insert intention lock

2. gap lock 相互不会阻塞

3. insert intention lock 相互不会阻塞

4. insert intention lock 也不会阻塞 gap lock

MySQL 5.7 及之前,可以通过 information_schema.innodb_locks 查看事务的锁情况。但,只能看到阻塞事务的锁;如果事务并未被阻塞,则在该表中看不到该事务的锁情况。从 MySQL 8.0 开始删除了 information_schema.innodb_locks 表,添加了 performance_schema.data_locks 表,可以通过 performance_schema.data_locks 查看事务的锁情况,和 MySQL 5.7 及之前不同,performance_schema.data_locks 不但可以看到阻塞该事务的锁,还可以看到该事务所持有的锁,也就是说即使事务并未被阻塞,依然可以看到事务所持有的锁(不过,performance_schema.data_locks 并不总是能看到全部的锁)。表名的变化其实还反映了 8.0 的 performance_schema.data_locks 更为通用了,即使你使用 InnoDB 之外的存储引擎,你依然可以从 performance_schema.data_locks 看到事务的锁情况。

在 performance_schema.data_locks 表的列 LOCK_MODE 表明了锁的类型。IS 或IX 表示意向锁、S,REC_NOT_GAP或X,REC_NOT_GAP 表示记录锁、S,GAP或X,GAP 表示间隙锁、S或X 表示next-key lock、S,GAP,INSERT_INTENTION或X,GAP,INSERT_INTENTION 表示插入意向锁。所以 performance_schema.data_locks 表可以更好滴帮助我们分析锁相关信息。

我们用下面三张图来说明 insert intention lock 的范围和特性。

MySQL InnoDB插入意向锁

上图演示了:T1 设置了 gap lock(11, 13) (13, 18),T2 设置了 insert intention lock(16, 18),两个锁的范围重叠了,于是 T1 gap lock(13, 18) 阻塞了 T2 insert intention lock(16, 18)。同样,如果你插入 (‘d’, 12) 也同样也会与 gap lock(11, 13) 重叠。

MySQL InnoDB插入意向锁

上图演示了:T1 设置了 insert intention lock(13, 18)、index record lock 13;T2 设置了 gap lock(17, 18)。尽管 T1 insert intention lock(13, 18) 和 T2 gap lock(17, 18) 重叠了。但,T2 并未被阻塞。因为 insert intention lock 并不阻塞 gap lock。

MySQL InnoDB插入意向锁

上图演示了:T1 设置了 insert intention lock(11, 18)、index record lock 11;T2 设置了 next-key lock(5, 11]、gap lock(11, 18)、PRIMARY 上的 index record lock ‘b’。此时:T1 index record lock 11 和 T2 next-key lock(5, 11] 冲突了,因此,T2被阻塞。

InnoDB 通常对插入操作无需加锁,而是通过一种“隐式锁”的方式来解决冲突。聚集索引记录中存储了事务 id。如果另外有个会话查询到了这条记录,会去判断该记录对应的事务 id 是否属于一个活跃的事务,并协助这个事务创建一个记录锁,然后将自己置于等待队列中。该设计的思路是基于大多数情况下新插入的记录不会立刻被别的线程并发修改,而创建锁的开销是比较昂贵的,涉及到全局资源的竞争。

在 RC 事务隔离级别下,虽然大多数插入操作是并发的,不会发生锁等待。然而,由于唯一约束或外键的存在,这时就需要加上插入意向锁。

<延伸>

读MySQL源码再看 INSERT 加锁流程


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (0)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!