在之前的一次开发需求中使用了 for update 实现悲观锁,最后导致出现了很多的 MySQL 死锁报警,现记录下死锁产生的原因。
业务中需要维护数据状态(例如进行中、失败、成功),但是这个状态是通过多条子任务最终的结果决定的,场景如下
根据上面场景可以想到,更新数据分为两个步骤
为了保证数据在并发情况下的正确性,当时想到的是保证查询和修改是一个原子性操作,所以决定在查询时使用 for update 对查询到的数据加锁。根据查询的结果修改任务的状态。但是后来发现这个修改逻辑造成 MySQL 死锁。
造成死锁的原因主要和 for update 对数据加锁的过程有些关系,加锁过程描述:
MySQL innodb 存储引擎默认的隔离级别时 RR 级别,而RR隔离级别,默认是使用Next-key Lock,Next-Key Lock加锁有下面几个规则。
表结构
mysql> show create table user;
CREATE TABLE `user` (
`id` int NOT NULL,
`score` int DEFAULT NULL,
KEY `socre` (`score`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci |
1 row in set (0.00 sec)
当前表结构,只有一个普通索引score。我们下面的测试都是在普通索引score的基础上完成的。
插入数据
复现案例
分析上图的两个事务的操作,在A事务加锁之后,实际的加锁范围是[10,30),sql查询流程如下:
经过上面的流程可以知道 for update 不仅会锁住查询到的数据, 也会锁住不满足查询条件的数据,当查询不到数据的时候甚至可能演变成表锁,因为不同事务的间隙锁可以重复加锁,所以当两个事务同时锁住某些相同的数据,并对这部分数据进行修改时就会出现死锁的情况
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。