空表并发INSERT必死锁的根本原因是InnoDB在REPEATABLE READ下启用Next-Key Lock,空表间隙为(-∞,+∞),多事务争抢同一Insert Intention Lock导致循环等待;推荐ON DUPLICATE KEY UPDATE或降级READ COMMITTED。

为什么空表或低数据量表并发INSERT必死锁
根本原因不是SQL写法问题,而是InnoDB在REPEATABLE READ隔离级别下默认启用Next-Key Lock(记录锁 + 间隙锁)。空表时主键/唯一索引的搜索范围是(-∞, +∞),所有并发INSERT都试图在同一个间隙加Insert Intention Lock——它本身兼容,但因无实际记录锚定,InnoDB锁队列机制会形成循环等待链。最终报错:Deadlock found when trying to get lock; try restarting transaction。
用INSERT ... ON DUPLICATE KEY UPDATE替代手动判断
这是最常用且安全的方案,前提是目标字段有UNIQUE KEY或PRIMARY KEY(比如order_no设为唯一索引)。
- 不依赖应用层
SELECT FOR UPDATE再INSERT,避免锁升级和顺序混乱 - 原子执行:存在则更新,不存在则插入;只锁命中行及其间隙,不触发额外锁等待链
- 即使只是“忽略”插入,也建议写一个无副作用的更新,例如
updated_at = VALUES(updated_at),防止InnoDB跳过锁优化
示例:INSERT INTO orders (order_no, user_id) VALUES ('ORDER123', 1001) ON DUPLICATE KEY UPDATE updated_at = VALUES(updated_at);
降级到READ COMMITTED隔离级别禁用间隙锁
如果业务能接受“不可重复读”,这是根治空表死锁最直接的方式——READ COMMITTED下InnoDB自动禁用间隙锁和Next-Key Lock,INSERT只锁定实际插入的行。
- 会话级生效:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; - 全局配置(需重启):
transaction_isolation = 'READ-COMMITTED'写入my.cnf - 代价明确:幻读可能重现(如事务A查不到某订单,B插入并提交后,A再查就看到了),但对多数幂等写入场景无实质影响
INSERT IGNORE和REPLACE INTO为什么不能解决死锁
它们看似“绕过冲突”,但底层仍要申请锁,反而可能加剧死锁风险。
-
INSERT IGNORE遇到唯一键冲突时,仍需先加Insert Intention Lock,再尝试获取冲突记录的共享锁(S锁),若此时另一事务持有X锁,就会卡在等待链中 -
REPLACE INTO本质是“删 + 插”,需先获取原记录X锁,再申请新行意向锁;两个事务同时操作同一键值,极易构成A删R1等插R2 / B删R2等插R1回路 - 尤其在二级唯一索引上,
REPLACE INTO还会额外对主键索引加锁,锁范围更大、持续时间更长
真正关键的不是换语法,而是控制锁行为:确保唯一索引字段走等值查询(=)、避免函数包裹或IN,并检查是否冗余建了唯一索引——多一层索引,就多一层间隙锁范围。


















