最有效手段是设innodb_autoinc_lock_mode=2且binlog_format=ROW;但仅对INSERT INTO t VALUES()类简单插入跳过锁,含SELECT/REPLACE/LOAD DATA的语句仍用语句级AUTO_INC表锁,导致高并发下排队阻塞。

直接改 innodb_autoinc_lock_mode=2 能缓解锁争用,但只对 INSERT INTO t VALUES () 类简单插入生效;含 SELECT、ON DUPLICATE KEY UPDATE 或混合模式的语句仍会卡表锁。
为什么 INSERT ... SELECT 一跑,其他插入就全堵住?
根本不是行锁或间隙锁的问题,是 InnoDB 在分配自增值时升级了锁粒度。默认 innodb_autoinc_lock_mode = 1 下,只要语句里带 SELECT(比如 INSERT INTO t SELECT * FROM s)、REPLACE SELECT 或 LOAD DATA,InnoDB 就会在执行前加语句级 AUTO_INC 表锁,整条语句结束才释放——哪怕只有一条这样的语句在跑,所有后续 INSERT 都得排队等。
现象很具体:show processlist 里一堆 Waiting for table level lock,慢日志里单条 INSERT 耗时从几毫秒跳到几百毫秒甚至秒级。
- 确认是否真有这类语句:查慢日志或
performance_schema.events_statements_history,过滤含SELECT的INSERT - 临时规避:把大
INSERT ... SELECT拆成小批量SELECT+ 多值INSERT,绕过语句级锁路径 - 别指望
innodb_autoinc_lock_mode=2能救它——mode=2 对这种语句照样退化为语句级锁
innodb_autoinc_lock_mode=2 的真实行为和硬限制
mode=2 是“预分配段”策略:一次拿一段 ID(如 100 个),事务回滚后不回收,导致主键空洞。这不是 bug,是设计取舍。
- 必须确保
binlog_format = ROW,否则主从复制可能中断;用SELECT @@binlog_format验证 - 不能依赖 ID 连续性:事务回滚、部分失败、重试都会扩大空洞;若业务用
ID+1推算下一条或做分页,会出错 -
INSERT ... ON DUPLICATE KEY UPDATE在 mode=2 下可能因预分配冲突触发内部重试,反而增加开销 - 只加速
INSERT INTO t VALUES (1),(2)这类纯值插入;INSERT INTO t (id, name) VALUES (NULL, 'a')算 mixed-mode,不享受无锁
不调参数,怎么从写法上绕开自增锁瓶颈?
参数只是工具,真正压垮并发的常是写入模式本身。与其死磕锁,不如换写法:
- 用多值
INSERT INTO t (a,b) VALUES (),(),()替代循环单条,每批控制在 100–500 行,显式事务包裹(BEGIN; ... ; COMMIT) - 高冲突场景慎用
ON DUPLICATE KEY UPDATE:唯一键冲突频繁时易升间隙锁,考虑先SELECT ... FOR UPDATE再决定INSERT或UPDATE - 超大批量导入优先用
LOAD DATA INFILE:绕过 SQL 解析层,快 3–10 倍,但注意secure_file_priv路径限制和编码一致性 - 如果业务允许,把自增主键换成应用层生成的 ID(如雪花 ID),彻底避开 InnoDB 自增锁逻辑
最容易被忽略的一点:空洞不可回填,且不影响索引结构或查询性能,但所有基于 ID 连续性的假设(比如分页推算、硬编码递增逻辑、监控告警阈值)都会失效。上线前务必检查业务代码里有没有这类隐含依赖。


















