`UPDATE ... WHERE ... ` InnoDB 中的多行锁定
`UPDATE ... WHERE ... ` multiple rows locking in InnoDB
我正在使用 InnoDB 引擎为 MySQL 数据库 v5.7.16 实现基于 table 的自定义序列生成器。
sequence_table
如下所示:
+-------------+-----------+
|sequence_name|next_value |
+-------------+-----------+
| first_seq | 1 |
+-------------+-----------+
| second_seq | 1 |
+-------------+-----------+
sequence_name
列是主键。
此序列 table 包含针对不同消费者的多个序列。
我使用以下策略进行序列更新:
- Select 当前序列值:
select next_val from sequence_table where sequence_name=?
。
- 将分配大小添加到当前序列值。
- 如果当前值与第一步中选择的值匹配,则更新序列值:
update sequence_table set next_val=? where sequence_name=? and next_val=?
。
- 如果更新成功return增加的序列值,否则从步骤1开始重复该过程。
文档包含以下信息:
UPDATE ... WHERE ... sets an exclusive next-key lock on every record
the search encounters. However, only an index record lock is required
for statements that lock rows using a unique index to search for a
unique row. 14.5.3 Locks Set by Different SQL Statements in InnoDB
粗体部分有点乱。
如您所见,我在 UPDATE
语句的 WHERE
子句中匹配主键。
搜索是否可能遇到多个记录并因此锁定此序列中的多行table?
换句话说,算法第3步的更新是只阻塞一行还是多行?
您没有提到您打算使用什么事务隔离级别。
假设您正在使用 repeatable read
(在 read committed
中不应该存在这样的问题)
来自here:
For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE),
UPDATE, and DELETE statements, locking depends on whether the
statement uses a unique index with a unique search condition, or a
range-type search condition
和
For a unique index with a unique search condition, InnoDB locks only
the index record found, not the gap before it
所以至少在理论上它应该只锁定一条记录并且不会使用 next-key 锁。
更多来自其他文档页面的引用来支持我的想法:
innodb-next-key-锁
A next-key lock is a combination of a record lock on the index record
and a gap lock on the gap before the index record.
间隙锁
Gap locking is not needed for statements that lock rows using a unique
index to search for a unique row
- 不要在主交易中抢序号;在
START TRANSCTION
. 之前做
- 使用
autocommit=ON
在一条语句中完成任务。
两者都使它更快,更不容易阻塞。
(您的代码缺少 BEGIN/COMMIT
和 FOR 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)
我正在使用 InnoDB 引擎为 MySQL 数据库 v5.7.16 实现基于 table 的自定义序列生成器。
sequence_table
如下所示:
+-------------+-----------+
|sequence_name|next_value |
+-------------+-----------+
| first_seq | 1 |
+-------------+-----------+
| second_seq | 1 |
+-------------+-----------+
sequence_name
列是主键。
此序列 table 包含针对不同消费者的多个序列。
我使用以下策略进行序列更新:
- Select 当前序列值:
select next_val from sequence_table where sequence_name=?
。 - 将分配大小添加到当前序列值。
- 如果当前值与第一步中选择的值匹配,则更新序列值:
update sequence_table set next_val=? where sequence_name=? and next_val=?
。 - 如果更新成功return增加的序列值,否则从步骤1开始重复该过程。
文档包含以下信息:
UPDATE ... WHERE ... sets an exclusive next-key lock on every record the search encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row. 14.5.3 Locks Set by Different SQL Statements in InnoDB
粗体部分有点乱。
如您所见,我在 UPDATE
语句的 WHERE
子句中匹配主键。
搜索是否可能遇到多个记录并因此锁定此序列中的多行table?
换句话说,算法第3步的更新是只阻塞一行还是多行?
您没有提到您打算使用什么事务隔离级别。
假设您正在使用 repeatable read
(在 read committed
中不应该存在这样的问题)
来自here:
For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE), UPDATE, and DELETE statements, locking depends on whether the statement uses a unique index with a unique search condition, or a range-type search condition
和
For a unique index with a unique search condition, InnoDB locks only the index record found, not the gap before it
所以至少在理论上它应该只锁定一条记录并且不会使用 next-key 锁。
更多来自其他文档页面的引用来支持我的想法:
innodb-next-key-锁
A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.
间隙锁
Gap locking is not needed for statements that lock rows using a unique index to search for a unique row
- 不要在主交易中抢序号;在
START TRANSCTION
. 之前做
- 使用
autocommit=ON
在一条语句中完成任务。
两者都使它更快,更不容易阻塞。
(您的代码缺少 BEGIN/COMMIT
和 FOR 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)