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