autocommit=1 是默认性能陷阱,每条 INSERT 触发完整事务闭环,10 万行即 10 万次 I/O;关闭自动提交并批量插入(BEGIN+多值 INSERT+COMMIT)可提速数十倍。

autocommit=1 是默认的性能陷阱
MySQL 默认开启自动提交,INSERT 语句不是“插一行”,而是“BEGIN → 插入 → COMMIT”三步闭环。每条记录都触发一次 redo log 写入、一次 fsync 刷盘、一次锁获取与释放、一次事务状态切换。10 万行 = 10 万次磁盘 I/O(或至少 10 万次日志缓冲区同步)。这不是数据库慢,是你在主动给它加了 10 万次“小任务”。
实测中,关闭自动提交后用 BEGIN 包裹 1000 条 INSERT 再 COMMIT,耗时可从 42 秒降到 1.7 秒——差的不是 SQL,是事务边界。
-
SELECT @@autocommit;必须先查,别假设生产环境已改 - ORM 框架(如 Spring)的
@Transactional不覆盖纯数据导入逻辑,不能依赖 - 应用层连接池里裸写
SET autocommit = 0后必须配对COMMIT或ROLLBACK,否则下次复用该连接会卡在未提交事务中
事务内日志刷盘和索引更新被批量合并
事务不是“把多条 SQL 堆一起”,而是让 InnoDB 把这 N 行的变更统一收口:redo log 只刷一次盘(取决于 innodb_flush_log_at_trx_commit),undo log 统一管理,B+ 树索引更新也延迟到 COMMIT 前一次性合并。避免了频繁页分裂和随机 I/O。
尤其当主键无序(如 UUID)时,单条插入极易引发 B+ 树节点反复分裂;而批量插入下,InnoDB 能更高效地预分配和重排页空间。
- 事务太长会撑爆
undo log,主从延迟加剧,甚至触发innodb_lock_wait_timeout - 推荐单次事务控制在 1000–5000 行,具体看单行大小(比如含大文本字段就得往下调)
- 避免在事务中混杂
SELECT ... FOR UPDATE或复杂子查询——它们会阻塞事务提交路径
多值 INSERT + 事务才是完整组合拳
光开事务不合并语句,只是省了刷盘,没省解析和网络开销。真正提速 10–50 倍的关键,在于把 1000 条单值 INSERT INTO t VALUES (a); INSERT INTO t VALUES (b); 改成一条多值语句:INSERT INTO t VALUES (a), (b), ..., (z);。这样只做一次语法解析、一次权限检查、一次执行计划生成,网络包也从 1000 个压成 1 个(只要不超过 max_allowed_packet)。
-
max_allowed_packet默认 4MB,插 10 万行容易超限,需提前设为 64M 或更高 - 主键冲突时整条语句失败,不如分批可控;可用
INSERT IGNORE或ON DUPLICATE KEY UPDATE缓冲 - 不要在存储过程里写
WHILE循环逐条INSERT——那是把客户端的低效逻辑搬进服务端,反而更慢
容易被忽略的底层卡点
即使代码层做了事务 + 多值合并,速度仍上不去?大概率卡在这些地方:
-
innodb_log_file_size太小,导致大事务频繁 checkpoint,吞吐反降 - 目标表有多个二级索引,但没在导入前
ALTER TABLE t DISABLE KEYS(MyISAM)或临时 DROP(InnoDB) - binlog 格式为
ROW,大事务生成巨量 binlog,压垮从库 - 用了 Seata 等分布式事务框架,
@GlobalTransactional让批量插入退化成 RPC 串行处理
事务快的前提,是长度可控、上下文干净、周边配置没拖后腿。别只盯着 SQL 写法,得看全链路。

















