`UPDATE ... WHERE ...`在InnoDB中锁定了多行

时间:2018-03-09 16:19:07

标签: mysql innodb

我使用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中的过程。
  5. 该文档包含以下信息:

      

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

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

    搜索是否可能遇到多个记录,因此在此序列表中锁定多行?

    换句话说,算法第3步中的更新会阻止一行还是多行?

2 个答案:

答案 0 :(得分:1)

您没有提到您计划使用的事务隔离级别。 让我们假设你正在使用repeatable read(在read committed中不存在这样的问题)

来自here

  

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

  

对于具有唯一搜索条件的唯一索引,InnoDB仅锁定   发现的指数记录,而不是之前的差距

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

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

innodb的-下一键锁

link

  

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

间隙锁

link

  

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

答案 1 :(得分:0)

  • 不要抓取主要交易中的序列号; 之前 START 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个nums并获取起始编号:

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)