空表并发INSERT必死锁,因RR级别下所有事务在(-∞,+∞)间隙争抢Insert Intention Lock,互斥导致循环等待;有效绕过方式包括降级到RC隔离级别、预占位缩小间隙、用REPLACE INTO或确保ON DUPLICATE KEY依赖唯一索引。

空表并发INSERT为什么必死锁
空表时InnoDB主键索引的间隙是(-∞, +∞),所有并发INSERT都得在这个超大间隙上申请Insert Intention Lock。而这个锁和Gap Lock互斥——哪怕只插一行,事务A和B同时执行INSERT INTO t(name) VALUES ('a'),就会互相等待对方释放间隙锁,形成死锁环。
现象是:一个成功、一个报Deadlock found when trying to get lock; try restarting transaction;重试后自增ID跳号(如1→3),说明中间值被回滚丢弃了。
SHOW ENGINE INNODB STATUS\G里能看到双方都在等PRIMARY上的X GAP锁。
INSERT ON DUPLICATE KEY UPDATE 也踩坑
它表面原子,但底层仍走完整唯一约束检查流程。只要存在多个唯一索引(比如UNIQUE(email)和UNIQUE(phone)),并发插入冲突值时,两个事务可能分别持有不同唯一索引的S锁,又互相申请对方的X锁,瞬间卡死。
- 错误写法:
INSERT INTO user (email, phone, name) VALUES ('a@b', '123', 'x') ON DUPLICATE KEY UPDATE name = VALUES(name) - 风险点:email和phone都是
UNIQUE,InnoDB加锁顺序不保证一致 - 更隐蔽的是:即使只用主键,只要显式指定非自增主键值(如
INSERT INTO t(id, name) VALUES (100, 'a')),也会触发gap锁检查
真正有效的绕过方式有哪些
死锁不是靠“重试”解决的,而是靠控制锁粒度、锁顺序和锁范围。重点不在“怎么扛”,而在“怎么绕”。
- 降隔离级别:把事务设为
READ COMMITTED,INSERT不再加Gap Lock,只锁匹配行——前提是业务允许读已提交 - 预占位:提前插入占位记录(如
INSERT IGNORE INTO t(id) VALUES (1)),缩小后续插入的间隙范围 - 用
REPLACE INTO替代(需确认业务语义兼容):它先删后插,不走Insert Intention Lock路径 - 强制走唯一索引:确保
ON DUPLICATE KEY依赖的字段有UNIQUE索引,否则查找阶段会锁住整个可能插入的间隙
为什么ORDER BY + LIMIT不能解决插入死锁
很多人以为INSERT ... SELECT ... ORDER BY id LIMIT 1能控制顺序,但插入意向锁的触发与排序无关——它是基于目标插入位置的间隙决定的,不是基于SELECT结果顺序。
真正起作用的是:是否命中唯一索引、是否在RR下触发间隙锁、是否让所有插入请求落在更小且分散的间隙中。
最容易被忽略的一点是:即使加了唯一索引,如果插入值本身落在同一个间隙(比如批量插入status=0且该字段无索引),所有事务仍会争抢同一段(-∞, +∞)或某个宽泛间隙,死锁照旧。


















