insert into select 卡住主因是repeatable read下全表或大区间next-key锁;应确保where命中主键/唯一索引、降隔离级至read committed、用多值insert或load data infile替代游标和单行操作。

INSERT INTO SELECT 为什么一跑就卡住线上写入?
不是它“锁表”,而是默认在 REPEATABLE READ 隔离级别下对源表扫描范围加 next-key lock,没走索引就等于锁全表,走了索引也锁一大片区间——其他事务想改同一段数据,直接排队等。
- 执行前必须先
EXPLAIN源查询,确认type是range或ref,rows扫描行数可控(比如几千以内) - WHERE 条件必须能命中主键或唯一索引,例如
WHERE id BETWEEN 1000001 AND 1010000,别用SELECT ... LIMIT做分页切分(MySQL 5.7 不支持子查询带 LIMIT 的 INSERT SELECT) - 临时把会话隔离级别降到
READ COMMITTED:执行前跑SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,锁范围大幅缩小,但得确认业务能接受非重复读
MySQL 存储过程里批量插入百万数据,游标是大忌
游标本质是单行 fetch + 单行 insert,每条都触发日志刷盘、索引更新、行锁争抢,10 万条实测比 while 循环还慢 3–5 倍,内存和锁开销爆炸,根本不能用。
- MySQL 没有
BULK INSERT(那是 SQL Server 的语法),写进去直接报ERROR 1064 - 真正可行的是字符串拼接多值
INSERT:每批 1000–2000 行,用CONCAT拼VALUES (),(),(),避免PREPARE/EXECUTE在循环里反复解析 - 插入前关掉约束检查:
SET UNIQUE_CHECKS = 0,插完再SET UNIQUE_CHECKS = 1;事务必须显式START TRANSACTION+COMMIT,别依赖自动提交
LOAD DATA INFILE 是 MySQL 里最快且不锁表的导入方式
它绕过 SQL 层解析,直接从文件流式写入引擎层,100 万行通常 10–20 秒搞定,且只对目标表加意向锁,不影响源表或其他并发写入。
- 文件必须放在 MySQL 服务端磁盘路径下(或加
LOCAL关键字从客户端上传,但需开启local_infile) - 导入前建议先
DROP INDEX,等数据进完再重建,否则每条记录都要维护索引树 - 如果数据含重复主键/唯一键,用
REPLACE INTO或IGNORE控制冲突行为,别让失败中断整个批次
ORM 批量插入 API 实际上在绕开 ORM 开销
所谓“批量 API”不是魔法,关键看它最终生成的是单条 INSERT 还是多值 INSERT。很多框架默认仍发 N 条语句,只是封装了参数绑定——性能没本质提升。
- 确认你用的 ORM 是否支持生成
INSERT ... VALUES (...), (...), (...)形式,而不是循环调用save() - 如果 ORM 不支持,不如直接拼 SQL +
execute,或者退一步用原生LOAD DATA INFILE或应用层流式拉取(如pt-archiver) - 别迷信“批量”二字,重点看网络往返次数、事务提交频率、是否触发唯一索引校验——这些才是瓶颈所在
LOAD DATA INFILE 仍是 MySQL 下最接近“无感”的方案——前提是文件可传到服务端。











