【问题标题】:`UPDATE ... WHERE ... ` multiple rows locking in InnoDB`UPDATE ... WHERE ...` InnoDB 中的多行锁定
【发布时间】:2018-03-09 16:19:07
【问题描述】:

我正在使用 InnoDB 引擎为 MySQL 数据库 v5.7.16 实现一个基于表的自定义序列生成器。
sequence_table 如下所示:

+-------------+-----------+
|sequence_name|next_value |
+-------------+-----------+
|  first_seq  |     1     |
+-------------+-----------+
|  second_seq |     1     |
+-------------+-----------+

sequence_name 列是主键。
此序列表包含针对不同消费者的多个序列。

我使用以下策略进行序列更新:

  1. 选择当前序列值:select next_val from sequence_table where sequence_name=?
  2. 将分配大小添加到当前序列值。
  3. 如果当前值与第一步中选择的值匹配,则更新序列值:update sequence_table set next_val=? where sequence_name=? and next_val=?
  4. 如果更新成功,则返回增加的序列值,否则从步骤 1 开始重复该过程。

文档包含以下信息:

UPDATE ... WHERE ... 在每条记录上设置一个排他的下一个键锁 搜索遭遇。但是,只需要一个索引记录锁 for 使用唯一索引锁定行以搜索 独特的行。 14.5.3 Locks Set by Different SQL Statements in InnoDB

粗体部分有点混乱。
如您所见,我匹配UPDATE 语句的WHERE 子句中的主键。

是否有可能搜索可能会遇到多条记录并因此锁定此序列表中的多行?

也就是说,算法第三步的更新会阻塞一行还是多行?

【问题讨论】:

  • 据我阅读this 了解到,它会锁定多行。我也相信你可以通过Isolation Levels控制行为。
  • 您是否正在尝试重新创建auto_increment 而没有所谓的间隙?
  • @flip,next-key lock locks only one index record and the gap before it。这是一种预期的行为,因为我没有定期向该表中插入任何内容,这意味着下一个键锁将在更新期间仅锁定一个索引记录(=一行)。但是搜索部分对我来说还不清楚。
  • @N.B.,应用程序代码需要有可能决定使用哪个序列来获取下一个值,auto_increment 不符合我的需要。在 PostgreSQL 中,我只有两个序列来完成这个。
  • 你会过得很糟糕。我不想暗示你应该使用auto_increment。祝你好运,真的。

标签: mysql innodb


【解决方案1】:

您没有提及您计划使用的事务隔离级别。 假设您使用的是repeatable read(在read committed 中不应该存在这样的问题)

来自here

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

对于具有唯一搜索条件的唯一索引,InnoDB 仅锁定 找到的索引记录,而不是之前的间隙

所以至少理论上它应该只锁定一条记录,并且不会使用下一个键锁。

来自其他文档页面的更多引用来支持我的想法:

innodb-next-key-locks

link

下一个键锁是索引记录上的记录锁的组合 并在索引记录之前的间隙上进行间隙锁定。

间隙锁

link

使用唯一值锁定行的语句不需要间隙锁定 搜索唯一行的索引

【讨论】:

    【解决方案2】:
    • 不要在主事务中获取序列号;在START TRANSCTIONSTART TRANSCTION 之前进行
    • 使用 autocommit=ON 在单个语句中完成任务。

    这两者都导致它更快,更不可能阻塞。

    (您的代码缺少 BEGIN/COMMITFOR UPDATE。我摆脱了这些而不是解释问题。)

    设置测试:

    mysql> CREATE TABLE so49197964 (
        ->     name VARCHAR(22) NOT NULL,
        ->     next_value INT UNSIGNED NOT NULL,
        ->     PRIMARY KEY (name)
        -> ) ENGINE=InnoDB;
    Query OK, 0 rows affected (0.02 sec)
    
    mysql> INSERT INTO so49197964 (name, next_value)
        ->     VALUES
        ->     ('first', 1), ('second', 1);
    Query OK, 2 rows affected (0.00 sec)
    Records: 2  Duplicates: 0  Warnings: 0
    
    mysql> SELECT * FROM so49197964;
    +--------+------------+
    | name   | next_value |
    +--------+------------+
    | first  |          1 |
    | second |          1 |
    +--------+------------+
    2 rows in set (0.00 sec)
    

    从 'first' 中获取 20 个数字并获取起始数字:

    mysql> UPDATE so49197964
        ->     SET next_value = LAST_INSERT_ID(next_value) + 20
        ->     WHERE name = 'first';
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql> SELECT LAST_INSERT_ID();
    +------------------+
    | LAST_INSERT_ID() |
    +------------------+
    |                1 |
    +------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT * FROM so49197964;
    +--------+------------+
    | name   | next_value |
    +--------+------------+
    | first  |         21 |
    | second |          1 |
    +--------+------------+
    2 rows in set (0.00 sec)
    

    再拿 20 个:

    mysql> UPDATE so49197964
        ->     SET next_value = LAST_INSERT_ID(next_value) + 20
        ->     WHERE name = 'first';
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql> SELECT LAST_INSERT_ID();
    +------------------+
    | LAST_INSERT_ID() |
    +------------------+
    |               21 |
    +------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT * FROM so49197964;
    +--------+------------+
    | name   | next_value |
    +--------+------------+
    | first  |         41 |
    | second |          1 |
    +--------+------------+
    2 rows in set (0.00 sec)
    

    【讨论】:

      猜你喜欢
      • 2022-11-16
      • 2017-02-01
      • 2011-10-05
      • 2018-07-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多