CreateInBatches 默认无事务,手动包裹 Transaction 反致性能下降10倍以上;应禁用默认事务、按批次单独启停事务,并显式处理错误回滚。

事务本身不提速,但错误的事务用法会让批量处理慢10倍以上——关键在控制粒度、跳过开销、避免嵌套。
为什么默认事务会让 CreateInBatches 变慢
GORM 的 CreateInBatches 默认不开启事务,但很多人会手动包一层 Transaction,以为“更安全”。实际这反而引入三重开销:事务日志刷盘、锁持有时间延长、连接池阻塞。尤其当批次设为 500、总数据 10 万时,单事务持续数秒,期间其他请求可能卡在获取连接上。
- 默认
CreateInBatches是无事务的纯 INSERT,失败则整批回滚(靠数据库语句原子性) - 显式
db.Transaction(...)会把全部批次塞进一个事务,违背“小事务”原则 - 微服务中上游可能已开启事务,
Transaction嵌套会触发 savepoint,MySQL 不支持真正的嵌套事务,行为不可控
真正有效的事务优化组合
批量写入要快,得让事务只做它该做的事:保证单批原子性,而非全局一致性。GORM 提供了细粒度控制能力,但需要主动启用。
- 用
db.Session(&gorm.Session{SkipDefaultTransaction: true})禁用 GORM 自动事务,避免无谓开销 - 对每一批次(如 200 条)单独开事务:
tx := db.Begin(); tx.CreateInBatches(batch, 200); tx.Commit() - 若需全局一致性,改用应用层幂等+补偿,而非长事务——比如插入前先
SELECT COUNT校验,失败则重试或告警 - 搭配
SkipHooks: true和QueryFields: false,避免钩子和字段反射拖慢每批次执行
CreateInBatches 的事务边界必须手动管理
这是最容易被忽略的点:GORM 不会帮你判断“这批插完要不要提交”。你传进去的切片再大,它也只管拼 SQL;事务开关、回滚逻辑、错误恢复,全得自己写。
- 不手动
Commit(),批次成功但连接未释放,下次CreateInBatches可能复用脏连接,导致主键错乱或死锁 - 某批次出错(如唯一键冲突),前面批次已
Commit,无法回滚——必须在循环内捕获 error 并调Rollback() - MySQL 下,
INSERT ... ON DUPLICATE KEY UPDATE类需求,不能依赖CreateInBatches,得用原生db.Exec+Prepare - PostgreSQL 支持
ON CONFLICT,但 GORM 的CreateInBatches不生成该语法,仍需绕过
批量事务不是“开个事务然后塞数据”,而是“按业务语义切分原子单元 + 显式控制每个单元的生命周期”。哪怕只是加一行 tx := db.Begin() 和两行 if err != nil { tx.Rollback() },也能避开 80% 的一致性陷阱和性能抖动。


















