MySQL间隙锁导致死锁案例分析与解决
请给出一个MySQL间隙锁(Gap Lock)导致死锁的真实案例,说明原因和解决方案。RR隔离级别下如何避免间隙锁引发的死锁?
回答
屠龙少年
死锁案例:
表结构:t(id INT PRIMARY KEY, name VARCHAR(20), KEY idx_name(name))
数据:(1, 'a'), (5, 'b'), (10, 'c')
事务A(先执行):
BEGIN;
-- 对id=7加Next-Key Lock (5,10] + 间隙锁 (5,10)
SELECT * FROM t WHERE id = 7 FOR UPDATE;
事务B(后执行):
BEGIN;
-- 尝试插入id=8,需在(5,10)间隙加插入意向锁
INSERT INTO t VALUES (8, 'd');
-- 被事务A的间隙锁阻塞,等待
事务A继续:
-- 尝试插入id=9
INSERT INTO t VALUES (9, 'e');
-- 也需在(5,10)间隙加插入意向锁
-- 发现事务B已持有插入意向锁的等待,自己也需要
-- 循环等待 → 死锁!
死锁原因:
- 事务A的SELECT FOR UPDATE给id=7加了间隙锁(5,10)
- 事务B的INSERT需要插入意向锁,被A的间隙锁阻塞
- 事务A的INSERT也需要插入意向锁,又被B的插入意向锁等待阻塞
- 循环等待形成死锁
解决方案:
- 降低隔离级别为RC:RC下没有间隙锁(但需解决幻读问题)
- 确保唯一索引命中:如果通过唯一索引等值查询命中记录,只加行锁不加间隙锁
- 控制加锁顺序:所有事务以相同顺序访问资源
- 使用乐观锁:通过版本号字段,更新时CAS检查
- 缩短事务时长:减少锁持有时间
- 死锁检测:MySQL自动检测并回滚代价小的事务(show engine innodb status查看)
生产建议:
- 对于高并发插入场景,考虑使用RC隔离级别
- 批量操作使用固定顺序(如ORDER BY id)