【问题标题】:Why does InnoDB block more records in case of a secondary index?为什么 InnoDB 在二级索引的情况下会阻止更多记录?
【发布时间】:2020-01-31 16:36:14
【问题描述】:

我正在使用 MySQL InnoDB 表并试图了解在索引范围扫描的情况下某些行级锁定的原因。我发现根据所使用索引的唯一性,可能会锁定额外的索引记录(超出范围)。请参见下面的示例(在 8.0.18 版本中验证)。

CREATE TABLE foo (
  a INT NOT NULL,
  b INT NOT NULL,
  c CHAR(1) NOT NULL,
  PRIMARY KEY (a),
  UNIQUE KEY (b)
) ENGINE=InnoDB;

INSERT INTO foo VALUES (1,1,'A'), (3,3,'B'), (5,5,'C'), (7,7,'D'), (9,9,'E');

测试用例 1

第 1 节:

START TRANSACTION;
SELECT * FROM foo WHERE a < 2 FOR UPDATE;

第 2 节:

DELETE FROM foo WHERE a = 3;  -- Success

测试用例 2

这将使用表的原始行并返回已删除的记录。

第 1 节:

START TRANSACTION;
SELECT * FROM foo WHERE b < 2 FOR UPDATE;

第 2 节:

DELETE FROM foo WHERE b = 3;  -- Blocks

在第二个测试用例中使用 b = 3 锁定二级索引记录看起来没有必要。

在二级索引的情况下,为什么 InnoDB 会阻止扫描范围右侧的下一个索引条目?这有什么实际原因吗? 如果 b = 3 的记录在第二个测试用例中没有被阻止,有人可以举一个可能发生的问题的例子吗?

【问题讨论】:

  • 在 bugs.mysql.com 提交错误

标签: mysql locking innodb


【解决方案1】:

我终于找到了答案。简而言之,在第二个测试用例中,这种额外的锁定没有明显的原因。当执行二级索引范围的锁定读取时,会设置足够的锁,但不是必要的最小值。因此,设置额外的锁只是因为一些 InnoDB 程序员更容易编写代码。如果一切正常,谁会关心额外的锁?

我发布了关于此问题的错误报告:https://bugs.mysql.com/bug.php?id=98639 不幸的是,他们的员工不想注册这个错误。他不理解我的论点,并提出了错误的解释。他将我最后的论点设为私密,并停止回应。

我也在官方论坛上询问过这个问题,得到如下答复:https://forums.mysql.com/read.php?22,684356,684482 简而言之,需要付出巨大的努力来修复这个错误。但由于这是一个非常小且无关紧要的错误(更准确地说,是一个性能问题),他们还不想修复它。但是,在 8.0.18 版本中,他们修复了聚集索引的类似问题,花了一个多月的时间。

我很惊讶优化这样一个简单的单级扫描算法需要这么多时间,而且对 MySQL 团队来说如此困难。

【讨论】:

    【解决方案2】:

    就像@Barmar 已经提到的那样,因为 MySQL 正在设置 GAP 或 Next-key 锁。我假设你使用的是默认的 innodb 隔离级别REPEATABLE READ

    MySQL 文档说:

    对于锁定读取(SELECT with FOR UPDATE 或 LOCK IN SHARE MODE)、UPDATE 和 DELETE 语句,锁定取决于语句是使用具有唯一搜索条件的唯一索引还是范围类型的搜索条件。

    • 对于具有唯一搜索条件的唯一索引,InnoDB 仅锁定找到的索引记录,而不锁定它之前的间隙。
    • 对于其他搜索条件,InnoDB 锁定扫描的索引范围,使用间隙锁或下一个键锁来阻止其他会话插入该范围所覆盖的间隙。有关间隙锁和下一个键锁的信息,请参阅第 14.7.1 节,“InnoDB 锁定”。

    见:https://dev.mysql.com/doc/refman/5.6/en/innodb-transaction-isolation-levels.html#isolevel_repeatable-read

    目前我正在寻找有关如何设置间隙锁定的信息,但我也得到了这个信息,我想这是有用的信息:

    请记住,锁定基于内部索引,因为您在条件中使用的列不是唯一索引的。

    间隙锁是索引记录之间的间隙上的锁,或第一个索引之前或最后一个索引之后的间隙上的锁 记录

    InnoDB 中的间隙锁是“纯粹的抑制性”,这意味着它们的唯一目的是防止其他事务插入到间隙中。间隙锁可以共存。一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。共享和独占间隙锁之间没有区别。它们彼此不冲突,并且执行相同的功能。

    可以显式禁用间隙锁定。 如果您将事务隔离级别更改为 READ COMMITTED 或启用 innodb_locks_unsafe_for_binlog 系统变量(现已弃用),则会发生这种情况。在这些情况下,间隙锁定对搜索和索引扫描禁用,仅用于外键约束检查和重复键检查。

    使用 READ COMMITTED 隔离级别或启用 innodb_locks_unsafe_for_binlog 还有其他影响。在 MySQL 评估 WHERE 条件后,不匹配行的记录锁将被释放。对于 UPDATE 语句,InnoDB 执行“半一致性”读取,这样它会将最新提交的版本返回给 MySQL,以便 MySQL 可以确定该行是否匹配 UPDATE 的 WHERE 条件。

    见https://dev.mysql.com/doc/refman/5.6/en/innodb-locking.html#innodb-gap-locks

    【讨论】:

    • 间隙锁定与我的问题无关。间隙锁不能阻止索引删除,只能阻止索引插入。在第二个测试示例中,b = 3 的索引记录被锁定(有或没有它的间隙,取决于隔离级别)。问题是为什么索引记录本身会被锁定。任何级别的隔离都会发生这种情况。假设我们使用 RR 隔离级别。为什么 InnoDB 在第二个测试用例中对 b = 3 的记录使用 next-key lock 而不是 gap lock?
    • 我整天都在寻找解释这一点,但我现在有点卡住了。您可以尝试查看innodb (lock) monitor,这可能会提供一些有价值的信息。我试过但很难理解输出。祝你好运!如果你弄清楚了,请告诉我。 :) dev.mysql.com/doc/refman/5.7/en/innodb-enabling-monitors.html
    • 锁监视器只允许查看设置了哪些锁。但它没有解释为什么要设置它们,也没有解释每个锁解决了什么问题。
    猜你喜欢
    • 1970-01-01
    • 2023-03-29
    • 2017-10-12
    • 2019-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-09
    相关资源
    最近更新 更多