应设 innodb_autoinc_lock_mode=2 且 binlog_format=row,可消除自增锁争用;insert ... on duplicate key update 卡顿主因是唯一键冲突导致记录锁队列;大批量插入宜用每批 500–1000 行的多值 insert 或 load data infile。

MySQL 8.0+ 怎么关掉 innodb_autoinc_lock_mode=1 的间隙锁开销?
默认的 innodb_autoinc_lock_mode=1(“连续”模式)在批量 INSERT ... SELECT、REPLACE 或带子查询的插入中,会持有表级自增锁直到语句结束,不是事务结束——这会让后续并发 INSERT 堵塞。真要降争用,得切到 2(“交错”模式),但必须确认 binlog 格式是 ROW,否则主从不一致。
-
SET GLOBAL innodb_autoinc_lock_mode = 2立即生效,但需SUPER权限,且重启后失效(得写进my.cnf的[mysqld]段) - 切之前检查:
SELECT @@binlog_format;必须是ROW;SELECT @@innodb_autoinc_lock_mode;确认当前值 -
mode=2下,自增值分配不再序列化,多个并发INSERT可能拿到不连续 ID,但无锁等待——这是可接受的代价
为什么 INSERT ... ON DUPLICATE KEY UPDATE 在高并发下反而更卡?
它本质是先做唯一键查找 + 插入尝试,失败再更新,整个过程需要持住对应二级索引记录的 X 锁和 insert intention 锁。当多线程反复撞同一个唯一键(比如用手机号做 UNIQUE),就会在那条记录上形成锁队列,后面请求全堵住。
- 避免高频撞同一条记录:把业务逻辑里“先查再插”的兜底逻辑,改成用
INSERT IGNORE或显式控制重试间隔 - 如果必须用
ON DUPLICATE KEY UPDATE,确保UNIQUE索引字段区分度足够高(别用状态位、类型码这种低基数字段) - 监控锁等待:
SELECT * FROM performance_schema.data_lock_waits;能看到谁在等哪条记录的锁
大批量插入时,INSERT VALUES (),(),() 和分批 INSERT 哪个更少锁?
单条多值 INSERT 是原子语句,InnoDB 会为整批预分配自增值,并在执行期间持有自增锁;而拆成多条单行 INSERT,每条只拿一个 ID,锁持有时间极短。但后者网络往返多、解析开销大,实际吞吐未必高——关键看批量大小。
- 实测经验:每批 500–1000 行较平衡;超过 5000 行,自增锁+日志刷盘压力明显上升
- 用
LOAD DATA INFILE替代手工拼INSERT,它走专用路径,自增锁只在开始和结束各持一次,中间批量分配 ID 不加锁 - 注意:
LOAD DATA需要FILE权限,且文件得在 MySQL 服务端本地(或开启local_infile)
自增 ID 用完了怎么办?还能不能继续 INSERT?
当 INT UNSIGNED 到 4294967295、BIGINT UNSIGNED 到 18446744073709551615 后,下一次 INSERT 会报错 ERROR 1467 (HY000): Failed to read auto-increment value from storage engine,不是跳回 1,也不是溢出变负——直接失败。
- 上线前必须估算数据量:比如日增 100 万,
BIGINT够用约 5 万年;INT只够 11 年,别图省事 - 已有表改类型:
ALTER TABLE t MODIFY id BIGINT UNSIGNED AUTO_INCREMENT;,但需锁表(8.0+ 可加ALGORITHM=INSTANT如果只改类型不改其他) - 不要依赖
auto_increment_offset/auto_increment_increment做分库分表 ID 拆分——那是给主从架构留的旧方案,现在有更靠谱的分布式 ID 生成器
真正卡住高并发插入的,往往不是自增锁本身,而是唯一索引冲突引发的记录锁扩散、或是批量语句意外拖长了锁生命周期。调参只是入口,得结合 SHOW ENGINE INNODB STATUS 里的 TRANSACTIONS 和 LOCK WAIT 段,定位具体哪条语句、哪个索引在挡路。











