innodb_autoinc_lock_mode=1时,simple insert用轻量互斥锁不阻塞,bulk insert用auto-inc表级锁会阻塞;迁移后sql类型变化易引发死锁,需通过show engine innodb status识别并采用两阶段插入等方案规避。

innodb_autoinc_lock_mode=1 时 Simple insert 和 Bulk insert 锁行为差异
MySQL 5.1.22+ 默认 innodb_autoinc_lock_mode=1,它把插入分两类处理:能预知行数的 Simple insert(如 INSERT INTO t VALUES (1),(2))用轻量级互斥锁,不阻塞其他插入;不能预知行数的 Bulk insert(如 INSERT ... SELECT、LOAD DATA)仍用传统 AUTO-INC 表级锁,会阻塞并发插入。迁移后若原有 SQL 突然变成 Bulk insert 类型(比如 WHERE 条件动态化、子查询变复杂),就可能因锁升级引发死锁。
常见诱因包括:
-
INSERT ... SELECT中SELECT部分用了未命中索引的条件,导致优化器放弃物化,触发 Bulk 模式 - ORM 拼出的
INSERT ... SELECT ... WHERE NOT EXISTS (...)实际执行计划被判定为非确定行数 - 从低版本迁移到高版本后,
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE在某些条件下也被归类为 Mixed-mode,触发 AUTO-INC 锁
如何快速识别是自增锁模式导致的死锁
查 SHOW ENGINE INNODB STATUS\G 输出中的 LATEST DETECTED DEADLOCK 段,重点看两处:
- 事务状态里是否含
setting auto-inc lock字样(这是最直接证据) - 死锁双方的 SQL 是否一方是
INSERT ... SELECT,另一方是普通INSERT或REPLACE - 锁等待类型是否为
auto-inc而非X或S行锁
如果满足以上任意两点,基本可锁定是 innodb_autoinc_lock_mode 切换引发的冲突,而非业务逻辑或索引缺失问题。
绕过自增锁死锁的三种实操方案
不建议直接调成 innodb_autoinc_lock_mode=0(回归串行插入,性能崩盘)或 =2(要求 ROW 格式 binlog,迁移中常不可控)。优先用以下方式解燃眉之急:
- 把易冲突的
INSERT ... SELECT改写为两阶段:先SELECT ... INTO @var获取主键值,再用INSERT VALUES (@var)—— 变成明确行数的 Simple insert - 对高频插入表,显式指定
INSERT ... VALUES (..., LAST_INSERT_ID()+1)并配合SELECT MAX(id)预占 ID(需应用层保证并发安全) - 在事务开头加
SELECT * FROM t WHERE id = 0 FOR UPDATE(空条件但命中主键),提前获取 AUTO-INC 锁,避免后续插入时升级争抢
迁移后必须检查的隐性陷阱
自增锁死锁往往不是孤立问题,而是暴露了更深层隐患:
-
INSERT ... SELECT语句里WHERE条件字段没索引 → 触发全表扫描 + Gap Lock 扩大范围,和 AUTO-INC 锁叠加形成复合死锁 - 应用层重试机制缺失:死锁报错
Deadlock found when trying to get lock后未自动重试,导致业务直接失败 - 复制环境 binlog_format 仍是 STATEMENT:设
innodb_autoinc_lock_mode=2会导致主从数据不一致,但设 =1 又留死锁风险,必须确认格式已切为 ROW
真正棘手的从来不是锁模式本身,而是迁移前后 SQL 执行计划漂移、索引覆盖变化、以及 binlog 格式与锁模式的隐式耦合 —— 这些地方不细查,光调参数只是把问题藏得更深。











