INSERT本身不会直接引发死锁,但在RR隔离级别下因Next-Key Lock与其它事务的SELECT FOR UPDATE、UPDATE或DELETE交叉执行且锁序不一致时,易形成循环等待导致死锁。

INSERT 操作本身不会直接引发死锁,但当它与其它事务的 SELECT ... FOR UPDATE、UPDATE 或 DELETE 交叉执行,并且锁获取顺序不一致时,死锁就很容易发生。根本问题不在 INSERT 语句写法,而在并发场景下锁的范围、粒度和顺序。
为什么 INSERT 会卷入死锁?
常见误区是认为 INSERT 只加行锁,实际上在可重复读(RR)隔离级别下,InnoDB 会对插入位置加 Next-Key Lock(行锁 + 间隙锁)。如果两个事务试图往同一索引间隙插入不同记录,又各自持有对方需要的锁,就会形成循环等待。
- 事务 A 执行
SELECT * FROM orders WHERE order_date = '2026-07-20' FOR UPDATE→ 锁住该值对应记录及前后间隙 - 事务 B 同时执行
INSERT INTO orders (order_id, order_date) VALUES (1001, '2026-07-20')→ 需要获取同一间隙的插入意向锁(insert intention lock) - 事务 C 又执行类似范围查询或更新 → 可能反向持有事务 B 需要的锁
此时三者可能构成环状依赖,InnoDB 检测后随机回滚一个事务并报错 Deadlock found when trying to get lock。
排查 INSERT 死锁的关键命令
死锁发生后,不能只看报错 SQL,必须定位到实际持锁和等锁的事务上下文。
- 立即执行
SHOW ENGINE INNODB STATUS\G,在TRANSACTIONS段中找LATEST DETECTED DEADLOCK,它会显示两个事务各自的 SQL、锁类型(如lock_mode X locks gap before rec)、索引名和具体锁住的索引值 - 若用 MySQL 5.7+,查
performance_schema.data_lock_waits表,重点关注BLOCKING_ENGINE_TRANSACTION_ID和REQUESTING_ENGINE_TRANSACTION_ID - 注意:
information_schema.INNODB_TRX只显示当前活跃事务,无法还原已回滚的死锁现场
INSERT 死锁的典型修复方式
修复不是改 INSERT 语法,而是调整事务行为和索引设计。
- 避免在 INSERT 前做不必要的
SELECT ... FOR UPDATE—— 如果只是为了防止重复插入,改用INSERT ... ON DUPLICATE KEY UPDATE或INSERT IGNORE - 确保所有业务逻辑以相同顺序访问索引:比如总是先按
user_id查询/更新,再按order_date插入,而不是有的先查日期、有的先查用户 - 检查二级索引是否包含大字段(如
VARCHAR(MAX)或TEXT):这类字段被INCLUDE进索引时,可能导致锁升级或锁范围扩大,删掉INCLUDE或改用更小字段类型可显著降低概率 - 对高频插入表,考虑关闭自增主键的
innodb_autoinc_lock_mode=2(交错模式),减少 AUTO_INC 锁争用
容易被忽略的细节
很多团队花时间优化 INSERT 语句本身,却漏掉了真正关键的点:死锁永远是多个事务协作失败的结果。单个 INSERT 永远不会死锁;真正危险的是它嵌套在长事务里、跟范围查询共用同一索引、或在应用层没做幂等控制导致重试放大冲突。监控时要盯住 INNODB_METRICS 中的 lock_deadlocks 和 lock_timeouts,二者比值突然升高,往往意味着锁策略出了系统性偏差。


















