数据导入、日志入库、报表明细落表这类场景里,慢的不一定是查询,反而可能是插入。单条 insert 写起来简单,但数据量一大,网络往返、SQL 解析、事务提交、日志刷盘和索引维护都会叠加成明显开销。
MySQL 批量插入优化可以先抓三个方向:合并 SQL、控制事务、有序写入。
一条 SQL 插入多行
最朴素的写法通常是:
insert into import_record(batch_no, user_id, content)
values ('20260702', 1001, 'a');
insert into import_record(batch_no, user_id, content)
values ('20260702', 1002, 'b');
批量写入时,更推荐合并成多值插入:
insert into import_record(batch_no, user_id, content)
values
('20260702', 1001, 'a'),
('20260702', 1002, 'b'),
('20260702', 1003, 'c');
这样做的收益主要来自三点:
- 减少客户端和数据库之间的网络往返。
- 减少 SQL 解析和执行准备次数。
- 降低 binlog、redo log 等日志提交频率。
但合并不是越大越好。单条 SQL 太长会碰到 max_allowed_packet,也会让失败重试成本变高。更稳的做法是按固定批次写入,比如每 500 条或 1000 条合并一次,再根据字段大小和线上表现调整。
放进事务里提交
如果每条插入都自动提交,数据库需要不断开启、提交事务,日志刷盘和事务管理开销会非常明显。
可以把一批插入放到同一个事务中:
start transaction;
insert into import_record(batch_no, user_id, content)
values
('20260702', 1001, 'a'),
('20260702', 1002, 'b');
insert into import_record(batch_no, user_id, content)
values
('20260702', 1003, 'c'),
('20260702', 1004, 'd');
commit;
事务批量提交能减少提交次数,但事务也不能无限变大。事务过大会带来锁持有时间变长、回滚成本变高、binlog 和 redo 压力增大等问题。
实践上可以按“批次可重试”来设计:每个批次有明确范围、失败后能定位、可以从断点继续,而不是把几十万行塞进一个不可控的大事务。
尽量按主键或索引顺序写入
InnoDB 使用 B+ 树组织索引。如果插入的数据在主键上基本有序,定位和页分裂成本会低很多;如果随机插入,可能频繁修改中间页,带来更多页分裂和磁盘 IO。
例如导入历史数据时,如果主键或业务唯一键可以排序,最好先在应用侧排序后再分批插入:
insert into order_event(id, order_id, event_time)
values
(10001, 'A001', '2026-07-02 10:00:00'),
(10002, 'A002', '2026-07-02 10:01:00'),
(10003, 'A003', '2026-07-02 10:02:00');
这不是说所有场景都必须强行排序。实时写入通常更关注业务链路简单和延迟稳定;离线导入、补偿任务、迁移任务更适合做有序写入优化。
批量插入要控制边界
批量优化容易从“提速”变成“制造大故障”,所以边界要写清楚。
建议至少控制这些参数:
- 每批行数:先从几百到几千测试,不要一开始就拉满。
- 单条 SQL 大小:避免超过
max_allowed_packet。 - 事务大小:避免长事务影响回滚、锁和复制。
- 失败重试:记录批次号、起止 ID 或源文件偏移。
- 限速策略:导入任务不要把线上数据库打满。
对于线上导入任务,最好额外记录审计日志:本批次写了多少行、耗时多少、失败原因是什么、下一批从哪里继续。
实用结论
MySQL 插入优化不是靠一个参数解决的。最常用的组合是:多值 insert 减少往返和解析,事务批量提交降低提交开销,有序写入减少索引维护成本。
真正上线时,重点不是把单次批量做得最大,而是让批次可控、失败可重试、速度可调、影响可观测。这样插入任务既能快起来,也不容易把数据库拖进不可控状态。



