MySQL中INSERT INTO SELECT在RR级别下因next-key锁+无索引扫描会等效锁源表;分批LIMIT OFFSET反而加剧锁压力;目标表自增主键还可能触发AUTO-INC表级锁;真正有效解法是应用层分批拉取再批量插入,或用SELECT INTO OUTFILE+LOAD DATA绕过事务锁链。

MySQL 中 INSERT INTO SELECT 不会“主动锁全表”,但极容易在默认配置下等效于锁表——根本原因是 REPEATABLE READ 隔离级别 + next-key lock + 无索引扫描的组合效应。
为什么 WHERE 条件没走索引就等于锁源表
在 REPEATABLE READ 下,SELECT 部分不是快照读,而是当前读(加锁读):InnoDB 会对所有扫描到的行及其间隙加 next-key lock(行锁 + 间隙锁)。一旦 WHERE 条件无法命中索引(比如用 WHERE DATE(created_at) = '2026-09-01' 或字段上根本没索引),就会触发全表扫描 → 所有行和间隙都被加锁 → 其他事务更新/删除这些行、或在任意间隙插入新数据,全部被阻塞。
- 哪怕只查 1 行,只要没索引,也可能扫完整张表(例如
ORDER BY ... LIMIT 1但没覆盖索引) -
SHOW ENGINE INNODB STATUS里能看到row lock(s)数量随执行时间飙升,动辄数万 - 这不是“语句设计问题”,而是 InnoDB 为防止幻读必须做的保守加锁
为什么分批用 LIMIT OFFSET 反而更糟
LIMIT OFFSET 在高并发、大数据量场景下不仅不缓解锁压力,还会放大问题:
-
OFFSET 100000意味着 MySQL 必须先定位并跳过前 10 万行,每批都重复扫描 → 锁持有时间线性增长 - 没有显式排序时,
OFFSET的结果顺序不保证,可能漏数据或重复处理 - 事务中多次执行
INSERT INTO SELECT ... LIMIT ... OFFSET ...,每次都会重新扫描、重新加锁,锁范围可能重叠扩大
为什么目标表自增主键会让问题雪上加霜
当目标表有 AUTO_INCREMENT 主键,且 innodb_autoinc_lock_mode = 1(MySQL 5.7+ 默认值),大批量 INSERT INTO SELECT 会触发较重的表级 AUTO-INC 锁:
- 该锁会阻塞其他任何对目标表的
INSERT(包括单行插入),而不仅限于当前语句 - 它和源表的
next-key lock是独立的两套锁机制,但会同时生效,加剧整体阻塞 - 即使源表加了索引、锁范围可控,目标表的
AUTO-INC锁仍可能成为瓶颈
真正有效的绕过方式只有两种
想彻底避开源表锁扩散,必须打破 “读-写耦合” 这个原子操作假设:
- 把
SELECT拆出来,在应用层拉取(如 Pythoncursor.fetchall()),再拼成INSERT VALUES (...), (...)批量写入 —— 源表只持短暂IS锁,且可控制批次大小 - 用
SELECT ... INTO OUTFILE导出,再LOAD DATA INFILE导入目标表 —— 绕过事务锁链,但要求有文件系统权限且不能跨库
临时表(CREATE TEMPORARY TABLE AS SELECT)只是把锁拆成两段,第一段仍锁源表;读写分离对 INSERT INTO SELECT 完全无效,因为它是写操作,必须走主库。


















