用INSERT INTO...VALUES(),(),...语法+临时禁用非主键索引可将3万条插入从20秒压至1秒内;需配合事务包裹、主键有序插入及谨慎禁用/重建非唯一索引。

直接上结论:用 INSERT INTO ... VALUES (), (), ... 语法 + 临时禁用非主键索引,能轻松把 3 万条插入从 20 秒压到 1 秒内。但禁用索引不是无脑关,顺序、时机、恢复方式都错不得。
用多值 INSERT 代替单条 INSERT
MySQL 解析一条 INSERT 语句的开销远高于执行它本身。单条插入每行都要走一次连接、解析、优化、写日志全流程;而批量插入把 N 行塞进一个语句里,只解析一次、只提交一次事务、日志合并写入,IO 和 CPU 开销直线下降。
- 错误写法:
INSERT INTO users (name, age) VALUES ('a', 18); INSERT INTO users (name, age) VALUES ('b', 19);—— 每条都是独立语句 - 正确写法:
INSERT INTO users (name, age) VALUES ('a', 18), ('b', 19), ('c', 20);—— 一行搞定三行数据 - 单次最多支持约 65535 个参数(受
max_allowed_packet限制),建议单批控制在 1000–5000 行,避免超限或内存压力过大 - 如果用 Python 的
executemany(),底层仍可能被驱动拆成多条单值语句,除非显式开启rewriteBatchedStatements=true(JDBC)或使用原生多值拼接
禁用索引前必须确认主键和唯一约束是否必要
非主键索引(如普通 INDEX 或 FULLTEXT)在插入时要同步更新 B+ 树,是性能杀手。但主键和唯一索引不能禁——它们保障数据一致性,禁了会直接报错或导致重复脏数据。
- 可安全禁用:
DROP INDEX idx_email ON users;(插入完再CREATE INDEX) - 不可禁用:
PRIMARY KEY、UNIQUE KEY email—— 这些必须保留,否则INSERT会因违反约束失败 - 如果表有外键,禁用索引前先
SET FOREIGN_KEY_CHECKS = 0;,插入完再设回1,否则外键字段上的索引操作可能被拦截 - 禁用后记得重建:
CREATE INDEX idx_created_at ON users(created_at);,不要依赖自动恢复
事务包裹 + 关闭 autocommit 是提速关键
即使用了多值 INSERT,如果每条语句还各自提交,redo log 刷盘开销依然巨大。必须手动控制事务边界,把所有批量插入包在一个 START TRANSACTION 里,最后统一 COMMIT。
- Java JDBC 中务必调用
connection.setAutoCommit(false),否则rewriteBatchedStatements=true也白搭 - MySQL 命令行或脚本中写法:
START TRANSACTION; INSERT INTO ... VALUES (...),(...); INSERT INTO ... VALUES (...); COMMIT; - 注意事务大小:单事务别超过 10 万行,否则可能触发
innodb_log_file_size溢出或锁等待加剧 -
innodb_flush_log_at_trx_commit = 0可进一步提速(牺牲极小概率崩溃丢数据),仅限导入阶段临时设,用完立刻还原
真正容易被忽略的是数据顺序:主键或聚簇索引字段尽量按递增顺序插入。乱序插入会导致大量页分裂和磁盘随机写,哪怕语法和事务都对了,速度也可能打五折。

















