mysql - MySQL 间隙锁行为不符合预期
问题描述
问题描述:
在mysql 8.0中,我启动了一个事务并执行了SELECT * FROM child WHERE id > 1000 FOR UPDATE;
(事务一),然后我启动了另一个事务并执行了update child set id = id+10 where id = 101;
(事务二),在表中有一行id = 101
child
,但是这个事务二被阻塞了。
如果我执行update child set id = id+10 where id = 102;
(事务三)并且表中不存在任何行id = 102
,事务三不会被阻塞并且可以成功执行。
据我所知,mysql 8.0 gap lock只会锁定id大于1000的行,但是在第二个事务中,row id是101且不大于1000,所以两个事务不会相互冲突。那么为什么事务二会被事务一阻塞呢?
附加细节如下:
- 表
child
结构:
CREATE TABLE `child` (\n `id` int NOT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
- 表中的所有数据
child
:
mysql root@localhost:test> select * from child;
+------+
| id |
+------+
| 90 |
| 101 |
| 105 |
| 106 |
| 109 |
| 111 |
| 1007 |
+------+
7 rows in set
- 所有 mysql 8.0 配置都是默认配置。
- 这两个交易是并行的。
解决方案
稍微简化一下,锁将始终附加到现有对象,例如一行。
假设您的表中有 ID 90、101、105、106、109、110 和 1007。请注意,我将您的示例行 111 更改为 110,否则,从 101 到 111 的更新将由于主键冲突而失败,然后才会遇到锁定问题。
如果 MySQL 需要为 发出锁WHERE id > 1000
,它不会(因为它不能)跟踪确切的值> 1000
。相反,它将采用最接近该值的现有对象,在您的情况下是 id 为 110 的行,并添加一个间隙锁,覆盖从 110 到 1006 的空间。另一个锁 + 间隙锁在 id 的行上1007,涵盖了 1007 及以上的空间。
是的,这覆盖了比需要更多的空间。但是该锁包括所需的空间,这是重要的方面。
您update child set id = id+10 where id = 101
现在会将行移动到锁定空间中,因此必须等待现有锁被释放。因此,由于 MySQL 的工作方式,这实际上是预期的行为。
如果您在 112 和 1000 之间有另一行,则间隙锁可以从那里开始,并且您的更新事务不必等待。
实际上,这是一个问题,尤其是对于小表(或特定的更新/插入模式)。如果添加越来越多的行,随机更新命中一个“过度覆盖”间隙的概率将会降低。
推荐阅读
- gitlab - GITLAB web IDE change default option - create a new branch
- elasticsearch - 批量索引期间 Elastic Search 是否会锁定查询?
- python - 在python中的多维数组上映射自定义函数
- javascript - Add active class to matching label of a checkbox
- git - `git subtree pull` 在主项目中产生奇怪的更改列表?
- flask - 在哪里放置初始化代码(在 Miogue Grinberg 的 Flasky 应用程序中)
- javascript - Mapping a compounded array from objects with JavaScript
- python - Dash python从Datepicker中选择多个日期
- c# - Json 反序列化数组
- java - Class java.nio.file.Files throws FileAlreadyExistsException as "optional specific exception". What does it mean?