RC隔离级别无法消除唯一键冲突引发的死锁,因间隙锁仍保留在外键约束和唯一性校验中;INSERT ... ON DUPLICATE KEY UPDATE在冲突行加NEXT-KEY LOCK,REPLACE INTO则在其下一条记录加锁,易形成等待环。

RC隔离级别不能消除唯一键冲突引发的死锁,这是最常被误判的核心点。
RC下间隙锁没完全消失,只退守到唯一性校验场景
很多人看到“RC关闭间隙锁”就以为锁范围缩到了最小,其实官方文档明确写了:间隙锁在RC下仅对SELECT和索引扫描失效,但依然保留在外键约束检查和DUPLICATE KEY冲突判断中。这意味着只要表上有主键或唯一索引,INSERT/REPLACE/ON DUPLICATE KEY UPDATE执行时,InnoDB仍会加NEXT-KEY LOCK——它本质是记录锁+间隙锁的组合,用来防止其他事务插入相同值。
典型表现就是死锁日志里反复出现gap before rec和insert intention waiting,说明事务正在等某个间隙上的X锁释放,而这个间隙锁正是由唯一键冲突触发的。
INSERT ON DUPLICATE KEY UPDATE 和 REPLACE INTO 的锁位置不同
这两类语句在RC下都可能死锁,但加锁位置有关键差异,容易被忽略:
-
INSERT ... ON DUPLICATE KEY UPDATE:冲突时,在**冲突行本身**加X型NEXT-KEY LOCK(例如冲突值是40,就锁(30,40]) -
REPLACE INTO:冲突时,在**冲突行的下一条记录**加X型NEXT-KEY LOCK(例如冲突值是40,实际锁的是(40,50])
这种错位会让两个并发事务锁定相邻但不重叠的区间,再叠加其他操作(比如先SELECT FOR UPDATE再INSERT),就极易形成A→B→A的等待环。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
唯一联合索引让死锁更隐蔽、更难复现
当表使用(a,b)这类唯一联合索引时,冲突检测不是按单列值比对,而是按索引前缀顺序逐级匹配。多个事务如果以不同顺序写入(比如一个按a升序,另一个按b升序),可能在索引树不同分支上分别持有GAP锁,最终因交叉等待触发死锁。
这种场景下,即使SQL看起来一样,只要输入参数的分布或排序逻辑不一致,就可能暴露问题。线上批量导入用循环单条REPLACE且key密集时,尤其高发。
降级RC只是降低概率,不是根治方案
把RR改成RC确实能砍掉大部分范围查询导致的间隙锁死锁,但它对唯一键冲突无能为力。真正可控的手段是收敛写入行为:
- 所有高频写唯一键的操作,确保key生成顺序固定(比如统一
ORDER BY后再批量处理) - 避免在同一个事务里混用
SELECT FOR UPDATE和INSERT ... ON DUPLICATE KEY UPDATE - 对分布式锁、计数器等强冲突场景,改用显式
SELECT FOR UPDATE+INSERT或UPDATE,绕过唯一性校验阶段的隐式锁
最易被忽略的一点是:死锁日志里显示“waiting for insert intention lock”,往往不是因为锁没抢到,而是因为前面有人持有了覆盖该插入位置的GAP锁——而这个GAP锁,恰恰来自你没意识到的唯一索引校验动作。










