不开事务+单条INSERT最慢;改用事务包裹+多值合并插入可提速10–50倍,因减少SQL解析、磁盘IO、锁开销及日志刷盘次数。

直接结论:不开事务 + 单条INSERT,是批量插入最慢的写法;只要把这两点改掉,90% 的场景下插入速度能提升 10–50 倍。
为什么单条 INSERT + 自动提交这么慢
MySQL 默认 autocommit=1,每执行一条 INSERT 就自动开启并提交一个事务。这意味着:每次都要写 redo log、刷盘、更新索引 B+ 树、加锁释放锁。哪怕只插 100 行,也是 100 次磁盘 IO 和 100 次 SQL 解析开销。
常见错误现象:SHOW PROCESSLIST 看到大量 query end 或 commit 状态卡住;监控显示 Innodb_log_waits > 0;插入耗时随数据量线性增长,毫无并发收益。
实操建议:
- 确认当前行为:
SELECT @@autocommit;—— 生产环境默认是1 - 不要依赖框架默认连接配置(如 Spring 的
@Transactional只包业务逻辑,不包纯数据导入) - 在导入脚本开头显式关闭:
SET autocommit = 0;,结尾再COMMIT;
怎么合并多条 INSERT 成一条语句
把 1000 条单独的 INSERT INTO t(a,b) VALUES (1,2); 改成一条含 1000 组值的语句,核心是减少网络往返和 SQL 解析次数。
示例对比:
-- 慢(1000 次解析 + 1000 次网络包) INSERT INTO user (id, name) VALUES (1, 'a'); INSERT INTO user (id, name) VALUES (2, 'b'); ... -- 快(1 次解析 + 1 次大包) INSERT INTO user (id, name) VALUES (1, 'a'), (2, 'b'), ..., (1000, 'z');
注意事项:
-
max_allowed_packet限制单条语句大小,默认 4MB,插 10 万行可能超限,需提前调大(如设为64M) - 值太多会增加客户端内存压力,建议单条控制在 1000–5000 行之间(视字段长度而定)
- 主键冲突时整条语句失败,不如分批可控;可用
INSERT IGNORE或ON DUPLICATE KEY UPDATE缓冲
事务粒度与批量大小怎么权衡
事务不是越大越好。太长的事务会占用大量 undo log、锁住更多记录、拖慢其他查询,甚至触发 innodb_lock_wait_timeout。
推荐做法:
- 每 1000–10000 行做一次
COMMIT,具体看单行大小和服务器内存 - 避免跨表大事务:不同表的插入尽量拆开事务,减少锁竞争
- 如果用
LOAD DATA LOCAL INFILE,它本身就是一个原子操作,无需额外套事务 - 注意 binlog 格式:
ROW模式下大事务生成巨量 binlog,可能压垮从库
容易被忽略的底层影响点
即使代码层做了事务 + 多值合并,性能仍上不去?大概率卡在这些地方:
-
innodb_flush_log_at_trx_commit = 1(默认)→ 插入时强制刷盘 redo log;临时导入可设为0,但断电会丢最多 1 秒数据 - 目标表有太多二级索引 → 每插一行,所有索引都要更新;导入前可
DROP INDEX,导完再重建 - 主键无序插入(如用 UUID 或随机数)→ 频繁页分裂,B+ 树重平衡;尽量按主键递增顺序提供数据
- 没关
foreign_key_checks和unique_checks→ 每行都校验外键和唯一约束,导入前可临时禁用
真正快的批量插入,从来不是只改 SQL 写法,而是代码层、配置层、表结构层同步动刀——其中最容易被跳过的,是禁用约束检查和调整日志刷盘策略。











