最直接有效的解法是批量提交事务+关闭autocommit:每条INSERT默认独立事务,导致10万次重复解析、加锁、写日志、刷盘,引发I/O瓶颈和锁争用;必须配合多值INSERT与START TRANSACTION/COMMIT显式控制,单批1000–5000行,并调大max_allowed_packet。

为什么单条INSERT插入10万行要2小时
不是磁盘慢,是每条INSERT都在重复做四件事:解析SQL、写redo log、刷盘提交事务、更新所有索引的B+树。autocommit=1时,这四步每行都来一遍,10万次就是10万次IO和锁竞争。
- 默认
autocommit=1,每行独立事务,日志刷盘无法合并 - 二级索引越多,每行插入触发的树分裂和页分裂越频繁
- 没主键的表,InnoDB会建隐藏聚簇索引,批量写入时争用更严重
- 客户端拼超长SQL字符串再执行,容易被MySQL截断或OOM,且无法参数化防注入
用INSERT ... VALUES(),()一次塞500–2000行
这是代码层成本最低、见效最快的优化,直接砍掉90%的网络往返和SQL解析开销。
- 单条语句行数别硬凑到5000+,要看
max_allowed_packet(默认4MB),建议先查:SHOW VARIABLES LIKE 'max_allowed_packet'; - 平均行大小200字节时,2000行≈400KB,留足缓冲;若字段含JSON或TEXT,得往下调到500行以内
- 务必用
PreparedStatement+addBatch()+executeBatch()(Java)或PDO的prepare()+execute(),别手动拼VALUES字符串 - 失败时整批回滚,但别因此把批次压到50行——回滚代价小了,总耗时反而飙升
必须关autocommit,用START TRANSACTION包住整批
不关autocommit,前面拼再多VALUES也没用,照样每行提交一次。
- 应用层显式执行:
SET autocommit = 0;或START TRANSACTION;,最后COMMIT; - 事务大小别贪大:单事务1万行以上,undo日志暴涨,主从延迟可能突增,崩溃恢复时间不可控
- 按时间或主键分片更稳妥,比如
WHERE created_at BETWEEN '2026-06-01' AND '2026-06-02',出问题能准确定位哪段失败 - 别在插入过程中跑
SELECT ... FOR UPDATE,会和INSERT争锁,拖慢整批速度
LOAD DATA INFILE比INSERT快5–20倍,但路径和权限常踩坑
它绕过SQL解析层,直连存储引擎,适合已有CSV/TSV文件的场景,但服务端读取路径和配置必须对齐。
-
LOAD DATA INFILE要求文件在MySQL服务器本地磁盘,路径是服务端视角,不是你本地机器 - 要用
LOAD DATA LOCAL INFILE?先确认服务端local_infile=ON,且客户端驱动允许(如MySQL Connector/J需加allowLoadLocalInfile=true) - 字段分隔符、行结束符必须和文件严格一致,否则
SHOW WARNINGS里全是“skipped”却无报错 - 导入前可临时关唯一检查:
SET unique_checks = 0;,但业务逻辑得自己校验重复,不能只靠数据库兜底
max_allowed_packet的协同——调大后者却不控制每批行数,会导致单语句解析卡顿;关了autocommit却用一个超大事务包全量,又会让监控失焦、回滚失控。这两者得一起调,而不是只改一个。



















