批量INSERT在RR级别下触发间隙锁,是因为InnoDB通过Next-Key Lock(行锁+间隙锁)防止幻读;空表时整个(-∞,+∞)被视作单一大间隙,导致并发插入争抢同一Insert Intention Lock而死锁;绕过核心是降级到RC、用INSERT ON DUPLICATE KEY或预占位。

为什么批量 INSERT 会触发间隙锁?
批量 INSERT 本身不直接加间隙锁,但当它在 REPEATABLE READ 隔离级别下执行、且目标表有唯一索引(或主键)时,InnoDB 会对插入位置的前后间隙加 Gap Lock,防止其他事务在该间隙插入相同值——这是 Next-Key Lock 的一部分。典型表现是:多个线程并发插入相邻主键值(如自增 ID 连续分配),SHOW ENGINE INNODB STATUS 中看到 *** (1) HOLDS THE LOCK(S) 指向 supremum pseudo-record。
- 空表或低数据量时最严重:整个主键范围 (-∞, +∞) 被视为一个大间隙,所有插入都争抢同一段
- 非自增字段做唯一键(如订单号按时间生成)也会触发,只要值接近就可能重叠间隙
-
INSERT ... SELECT或REPLACE INTO同样适用,且因语句级锁放大影响
怎么绕过间隙锁而不是硬扛?
核心思路不是“减少锁”,而是让 InnoDB 不需要加间隙锁——关键在于避免让插入行为落入“需防幻读”的判定路径。
- 把隔离级别降到
READ COMMITTED:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED。此时 InnoDB 禁用间隙锁,只锁实际插入的行。代价是可能产生幻读,但多数写多读少场景(如日志表、订单创建后不二次校验)可接受 - 用
INSERT IGNORE或REPLACE INTO替代先查后插:它们原子执行,不引入额外的SELECT FOR UPDATE锁链;但要求字段有UNIQUE KEY或PRIMARY KEY - 对非核心导入任务,临时关闭唯一检查:
SET UNIQUE_CHECKS=0(仅限单次批量导入,且必须确保无重复数据)
批量 INSERT 写法本身怎么降低锁竞争?
即使开了 READ COMMITTED,写法不当仍会导致记录锁排队。重点是控制“锁命中点”的集中度和事务粒度。
- 按主键升序排序后再批量插入:比如
INSERT INTO t (id, name) VALUES (1,'a'),(3,'c'),(2,'b')→ 改为(1,'a'),(2,'b'),(3,'c'),减少间隙重叠概率 - 每批控制在 500–1000 行:太大易触发锁升级(InnoDB 默认超 5000 行可能升表锁);太小则网络和解析开销抵消收益
- 避免高频撞同一条唯一键:比如用手机号做
UNIQUE,并发插入同一号码会全堵在那条记录的 X 锁上;区分度低的字段(状态位、类型码)别建唯一索引 - 禁用
autocommit,每批结束后COMMIT:否则锁一直持有,后续批次无法并行
哪些配置和参数要同步调?
光改 SQL 写法不够,底层机制得匹配,否则优化会被抵消。
- 确认
innodb_autoinc_lock_mode=2且binlog_format=ROW:否则INSERT ... SELECT类批量操作仍卡表级自增锁。检查命令:SELECT @@binlog_format和SELECT @@innodb_autoinc_lock_mode - 增大
max_allowed_packet:批量值列表过长会报错,建议设为67108864(64MB) - 不要依赖 ID 连续性:mode=2 下预分配 ID 会导致空洞,若业务用 ID 做分页推算或硬编码递增逻辑,会出问题
真正容易被忽略的是:间隙锁冲突往往不是孤立问题,而是和唯一索引设计、写入模式、隔离级别三者咬合在一起。单独调一个参数或换一种写法,常收效甚微;必须同时检查这三点是否协同。比如用了 ON DUPLICATE KEY UPDATE 却没配唯一索引,或者降了隔离级别却还在用 SELECT FOR UPDATE,反而更卡。


















