无法使用闩锁类型 sh 读取并闩锁页_InnoDB数据锁第2.5部分“锁”(深入研究)...

作者:Kuba Łopuszański 译:徐轶韬

现在,我们将InnoDB数据锁-第2部分“锁”中了解到的所有知识放在一起,进行深入研究:

mysql> BEGIN;Query OK, 0 rows affected (0.00 sec)mysql> SELECT * FROM t FOR SHARE;+----+| id |+----+|  5 || 10 || 42 |+----+3 rows in set (0.00 sec)mysql> DELETE FROM t WHERE id=10;Query OK, 1 row affected (0.00 sec)mysql> INSERT INTO t VALUES (4);Query OK, 1 row affected (0.00 sec)mysql> SELECT INDEX_NAME,LOCK_TYPE,LOCK_DATA,LOCK_MODE       FROM performance_schema.data_locks WHERE OBJECT_NAME='t';+------------+-----------+------------------------+---------------+| INDEX_NAME | LOCK_TYPE | LOCK_DATA              | LOCK_MODE     |+------------+-----------+------------------------+---------------+| NULL       | TABLE     | NULL                   | IS            || PRIMARY    | RECORD    | supremum pseudo-record | S             || PRIMARY    | RECORD    | 5                      | S             || PRIMARY    | RECORD    | 10                     | S             || PRIMARY    | RECORD    | 42                     | S             || NULL       | TABLE     | NULL                   | IX            || PRIMARY    | RECORD    | 10                     | X,REC_NOT_GAP || PRIMARY    | RECORD    | 4                      | S,GAP         |+------------+-----------+------------------------+---------------+8 rows in set (0.00 sec)

我们看到:

  • 第一个SELECT * FROM t FOR SHARE;在5、10、42和supremum pseudo-record上创建S锁(在间隙和记录上)。这意味着整个轴都被锁覆盖。而这正是所需的,可以防止任何其他事务修改此查询的结果集。同样,这需要先对表t加IS锁。

  • 接下来,DELETE FROM t WHERE id=10;首先获得的IX表锁以证明它打算修改表,然后获得的X,REC_NOT_GAP修改ID=10的记录

  • 最后,INSERT INTO t VALUES (4);看到它已经具有IX,因此继续执行插入操作。这是非常棘手的操作,需要谈谈我们已抽象的细节。首先从临时闩锁 (注意单词:“ latching”,而不是“ locking”!)开始,查看页面是否是放置记录的正确位置,然后在插入点右侧闩住锁系统队列并检查是否有*,GAPSX锁。我们的例子中没有记录,因此我们立即着手插入记录(它有一个隐式锁,因为它在“last modified by”字段中有我们的事务的id,希望这解释了为什么在记录4上没有显式的X,REC_NOT_GAP锁)。相反的情况是存在一些冲突的锁,为了显式地跟踪冲突,将创建一个等待的INSERT_INTENTION锁,以便在授予操作后可以重试。最后一步是在轴上插入新点会将已经存在的间隙分成两部分。对于旧间隙,已经存在的任何锁都必须继承到插入点左侧新创建的间隙。这就是我们在第4行看到S,GAP的原因:它是从第5行的S锁继承的。

这只是涉及到的真正复杂问题的冰山一角(我们还没有讨论从已删除的行继承锁,二级索引,唯一性检查..),但是从中可以得到一些更深层次的想法:

  • 通常,要提供可串行性,您需要“锁定所见内容”,这不仅包括点,而且还包括点之间的间隙。如果您可以想象查询在扫描时如何访问表,那么您大都可以猜测它将必须锁定什么。这意味着拥有良好的索引很重要,这样您就可以直接跳到要锁定的点,而不必锁定整个扫描范围。

  • 反之亦然:如果您不关心可串行性,您可以尝试不锁定某些东西。例如,在READ COMMITTED隔离级别较低的情况下,我们尝试避免锁定行之间的间隙(因此,其他事务可以在行之间插入行,这会导致所谓的“幻读”)

  • 在InnoDB中,所有那些“正在插入”和“正在删除”的行,实际上都存在于索引中,因此出现在轴上并将其分成多个间隙。这与某些其他引擎形成对比,其他引擎将正在进行的更改保留在“暂存区”中,并且仅在提交时将其合并。这意味着即使在概念上并发事务之间没有交互(例如,在提交事务之前,我们不应该看到行被事务插入),但在低级别实现中,它们之间的交互仍然很多(例如,事务可以在尚未正式存在的行上有一个等待锁)。因此,看到Performance_schema.data_locks报告尚未插入或已被删除的行,不需要感到惊讶(后者将最终被清除)

记录锁的压缩(以及丢失的LOCK_DATA)

在上面的示例中,您看到了一个非常有用的LOCK_DATA列,该列为您显示了放置记录锁的索引列的行值。这对于分析情况非常有用,但是将“ LOCK_DATA”显式存储在内存对象中会很浪费,所以当你查询performance_schema时,这些数据实际上是实时重建的。data_locks表来自锁系统内存中可用的压缩信息,它与缓冲池页面中的可用数据结合在一起。也就是说,锁系统根据记录所在的页面和页面中的记录heap_no编号来标识记录锁。(这些数字通常不必与页面上记录值的顺序相同,因为它们是由小型堆分配器分配的,在删除、插入和调整行大小时,尽量重用页面内的空间)。这种方法具有一个很好的优点,即可以使用三个固定长度的数字来描述一个点:space_id, page_no, heap_no。此外,一个查询必须在同一页上锁定几行是一个常见的情况,所有锁(仅heap_no不同)都一起存储在一个有足够长的位图的单一对象,这样heap_no第一位可以表示给定记录是否应被此锁实例覆盖。(这里需要权衡取舍,因为即使我们只需要锁定一条记录,我们也会“浪费”整个位图的空间。值得庆幸的是,每页记录的数量通常足够小,您可以负担n / 8个字节)

因此,即使Performance_schema.data_locks分别报告每个记录锁,它们通常也仅对应于同一对象中的不同位,并且通过查看OBJECT_INSTANCE_BEGIN列可以看到:

> CREATE TABLE t(id INT PRIMARY KEY);> insert into t values (1),(2),(3),(4);> delete * from t where id=3;> insert into t values (
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值