INSERT ... SELECT 卡住其他插入是因为 innodb_autoinc_lock_mode=1 时升级为表级 AUTO-INC 锁,阻塞所有需新自增值的并发 INSERT;设为 2 可避免该锁但要求 binlog_format=ROW,且会导致自增 ID 空洞。

为什么 INSERT ... SELECT 会卡住其他插入
因为 innodb_autoinc_lock_mode=1(默认)下,这类语句会升级为表级 AUTO-INC 锁,阻塞所有需要新自增值的并发 INSERT。不是锁行,是锁“下一个 ID 的分配权”。
- 典型场景:
INSERT INTO t2 SELECT * FROM t1或带子查询的批量插入 - 只要语句长度不可预知(比如没指定具体值、也没用
VALUES明确列出全部行),InnoDB 就不敢用轻量的 mutex,转而拿重锁 - 影响范围不限于同一张表——如果多个表共用同一个 auto-increment 缓存段(极少见),也可能间接干扰
怎么安全地调高 innodb_autoinc_lock_mode
设成 2 可彻底避免 AUTO-INC 表锁,但要求 binlog_format=ROW。否则主从不一致:语句模式下从库重放时无法复现主库分配的 ID 序列。
- 确认当前 binlog 格式:
SELECT @@binlog_format,必须是ROW - 动态生效(重启不丢):
SET GLOBAL innodb_autoinc_lock_mode = 2 -
innodb_autoinc_lock_mode=0是传统表锁模式,已基本淘汰,别用 - 注意:
INSERT ... ON DUPLICATE KEY UPDATE和REPLACE在 mode 2 下仍可能触发间隙锁竞争,和 autoinc 无关,别混淆
自增 ID 空洞不是 bug,是 mode 2 的必然代价
设成 2 后,如果事务 A 预占了 100~199,事务 B 预占了 200~299,但 A 回滚了,那 100~199 就永远空着。这不是浪费,是并发安全的必要开销。
- 空洞不影响查询、索引效率,InnoDB 不靠 ID 连续性做任何优化
- 如果业务强依赖“无跳号”(比如对外暴露 ID 当单号),就别碰
innodb_autoinc_lock_mode,老实用mode=1+ 拆小批插入 - 监控空洞程度可用:
SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='db' AND TABLE_NAME='t'对比实际最大 ID
INSERT ... VALUES() 批量插入也踩坑?
单条 INSERT 插多行(如 INSERT INTO t VALUES (1),(2),(3))在 mode=1 下是安全的,用 mutex 分配 ID;但如果你写成三条独立 INSERT,哪怕内容一样,就会触发三次锁申请——性能差,还容易锁等待。
- 能合尽量合:
INSERT INTO t VALUES (1),(2),(3),...,(1000)—— 这种最多一次锁 - 超过 1000 行建议分批,每批 500~1000,避免单语句解析/网络传输压力过大
- ORM 框架(如 Django ORM、MyBatis)默认可能拆成单行 INSERT,得查文档或抓包确认生成的 SQL
真正难处理的是混合场景:一边有长事务拿着 AUTO-INC 锁不放,一边大量短 INSERT 堵在后面。这时候看 SHOW ENGINE INNODB STATUS 的 TRANSACTIONS 部分,找状态为 LOCK WAIT 且 waiting for auto-inc 的线程——它等的不是某一行,是整个表的 ID 发放权。










