INSERT INTO SELECT 性能瓶颈源于读写事务约束四重开销叠加,需分批插入、禁用非关键索引与外键检查、优化源表查询计划。

因为 INSERT INTO SELECT 不是单纯的“读”,而是“读+写+事务+约束校验”四重开销叠加,数据量每翻一倍,写放大、日志生成、索引更新、锁持有时间几乎都同步翻倍——这不是算法复杂度问题,是底层 I/O 和并发模型决定的硬瓶颈。
目标表索引和约束是隐形拖累
SELECT 很快,但 INSERT 阶段每行都要触发:所有二级索引更新、唯一键校验、外键检查、触发器执行。哪怕只加 1 个额外索引,写入耗时就可能翻倍。
-
SHOW CREATE TABLE target_table查看索引数量和类型,超过 3 个二级索引就要警惕 - 临时禁用非关键索引:
ALTER TABLE target_table DISABLE KEYS(仅 MyISAM 有效);InnoDB 只能先DROP INDEX,插入完再重建 - 批量插入前关掉外键检查:
SET FOREIGN_KEY_CHECKS = 0,结束后再设回1
单一大事务撑爆日志和内存
默认情况下,整个 INSERT INTO SELECT 在一个事务里完成。200 万行 = 200 万行 REDO + UNDO 日志,innodb_log_file_size 不够就会频繁刷盘,innodb_buffer_pool 压力飙升,甚至触发磁盘 swap。
- 用
LIMIT分批,例如每次插 5000 行:INSERT INTO t2 SELECT * FROM t1 WHERE id > ? ORDER BY id LIMIT 5000 - 每批后显式
COMMIT,避免长事务阻塞其他操作 - 会话级调优(仅限可接受数据短暂不一致的场景):
SET innodb_flush_log_at_trx_commit = 2
源表扫描方式被忽略
很多人只优化目标表,却忘了 SELECT 部分如果走全表扫描(type: ALL),CPU 和 IO 负载早就在源头拉满了——尤其当 WHERE 条件没走索引、JOIN 没走驱动表时。
- 务必先
EXPLAIN检查 SELECT 部分的执行计划,重点看key是否命中、rows是否远超预期 - WHERE 中避免
!=、OR、函数包裹字段(如DATE(create_time)),这些会让索引失效 - 如果源表有
ORDER BY或GROUP BY,确认是否真有必要——它们常导致Using temporary和Using filesort
真正卡住性能的,往往不是某一行 SQL 写得不够漂亮,而是没意识到 INSERT INTO SELECT 是把“查询压力”和“写入压力”绑死在同一根线上。拆开它,比调优它更有效。


















