RC隔离级别不能消除死锁,根本原因在于唯一键冲突检测仍触发NEXT-KEY LOCK(Record Lock + Gap Lock),并发执行INSERT ON DUPLICATE KEY UPDATE或REPLACE INTO时因加锁位置不同(冲突行本身 vs 下一条记录)易形成循环等待,导致死锁。

RC隔离级别不能消除死锁,根本原因在于:唯一键冲突检测仍会触发NEXT-KEY LOCK,而这类锁在并发写入时极易形成循环等待。
RC下间隙锁真的消失了?
很多人看到“RC禁用间隙锁”就以为gap lock彻底没了,其实官方文档明确写了:gap locking is used only for foreign-key constraint checking and duplicate-key checking。也就是说,只要涉及主键或唯一索引的冲突判断(比如INSERT ... ON DUPLICATE KEY UPDATE或REPLACE INTO),InnoDB仍会在冲突位置加NEXT-KEY LOCK——它本质是Record Lock + Gap Lock的组合,不是纯记录锁。
典型表现是死锁日志里反复出现:gap before rec、insert intention waiting。这说明事务正在等一个GAP锁释放,而该锁来自另一事务对唯一值的校验过程。
INSERT ON DUPLICATE KEY UPDATE 和 REPLACE INTO 的锁行为差异
这两条语句在RC下都可能死锁,但加锁位置不同,容易被忽略:
-
INSERT ... ON DUPLICATE KEY UPDATE:冲突时,在**冲突行本身**加X型NEXT-KEY LOCK -
REPLACE INTO:冲突时,在**冲突行的下一条记录**加X型NEXT-KEY LOCK(例如插入值40发生冲突,则锁区间(40,50]而非(30,40])
这个细微差别会让两个事务实际锁定的索引区间错位,反而更容易交叉等待。比如事务A锁(30,40],事务B锁(40,50],又都试图插入40,就可能因等待链成环被InnoDB判定为死锁。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
唯一联合索引是高危场景
当表上有UNIQUE INDEX (a,b)时,并发写入不同a但相同b的值,或按不同顺序命中前缀(如事务1查a=1再插(1,100),事务2查a=2再插(2,100)),InnoDB可能在不同间隙加锁,且因索引扫描路径不一致,导致锁顺序不可控。
更危险的是混合操作:事务内先SELECT ... FOR UPDATE读某范围(RC下该语句不加gap lock),随后执行INSERT ... ON DUPLICATE KEY UPDATE——后者会因唯一检查突然加锁,而该锁位置可能与前面读操作的隐式定位重叠,引发意外阻塞。
真正有效的缓解手段不是换隔离级别
把RR改成RC只是降低死锁频率,无法根除。关键要收敛写入逻辑:
- 所有写唯一键的操作,确保key值按固定顺序生成(如批量
REPLACE前先ORDER BYkey) - 避免在同一个事务里混用
SELECT ... FOR UPDATE和INSERT ... ON DUPLICATE KEY UPDATE - 高频冲突场景(如分布式锁表),改用显式
SELECT ... FOR UPDATE+INSERT或UPDATE,绕过自动冲突检测路径 - 检查是否缺失索引——
UPDATE或DELETE没走索引会导致全表扫描+大量行锁,极大增加死锁概率
最常被忽略的一点:死锁日志里的insert intention waiting不是说“还没抢到插入权”,而是暴露了GAP锁已被持有;此时问题不在SQL怎么写,而在谁先持有了那个本不该长期持有的间隙范围。

















