createinbatches比循环create快得多,因其底层生成单条多值insert语句,避免n次连接/解析/事务开销;推荐batch_size设为100–500,需避mysql max_allowed_packet、postgresql returning错位及sqlite退化问题;默认无事务,须手动包裹transaction以防部分失败;rowsaffected仅返回最后一批行数,主键回填依赖数据库能力(mysql不支持returning,postgresql需显式指定,sqlite不可靠)。

为什么 CreateInBatches 比循环 Create 快得多
因为 CreateInBatches 底层生成的是单条多值 INSERT INTO users (name, email) VALUES (), (), () 语句,而循环调用 db.Create(&user) 会发出 N 条独立 INSERT,每条都走完整连接、解析、执行、事务提交流程。实测插入 10 万条时,后者耗时通常是前者的 8–10 倍,且极易触发连接池耗尽或 MySQL 的 max_connections 限制。
CreateInBatches 的 batch_size 设多少才合理
推荐设为 100–500,不是越大越好:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 太小(如
10):批次过多,抵消不了批量优势,还增加调度开销 - 太大(如
5000):可能突破 MySQL 的max_allowed_packet(默认 4MB),或导致单次内存分配过大 - PostgreSQL 下若开启
RETURNING,大批次还会加剧结果错位风险 - SQLite 会直接退化为循环插入,
batch_size几乎无意义
不加事务的 CreateInBatches 很危险
CreateInBatches 默认不开启事务,某一批失败时,前面批次已提交,数据处于中间状态:
- 迁移脚本中部分成功 → 后续重跑会主键冲突或重复数据
- 后台任务中断 → 状态无法回滚,需人工修复
- 正确做法是显式包裹:
db.Transaction(func(tx *gorm.DB) error { return tx.CreateInBatches(&users, 300).Error }) - 别依赖
.RowsAffected判断整体成败——它只返回最后一批影响行数
主键回填在不同数据库表现差异极大
插入后发现 users[0].ID 还是 0?这不是 bug,是数据库能力限制:
- MySQL 完全不支持
RETURNING,CreateInBatches无法回填 ID,必须后续查或接受“无回填” - PostgreSQL 支持,但仅限单批且字段需显式指定:
db.Clauses(clause.Returning{Columns: []clause.Column{{Name: "id"}}}).CreateInBatches(...) - SQLite 不稳定,即使写对语法也常返回空,官方文档明确标注“不保证”
- 所有场景下,
CreatedAt/UpdatedAt字段仍会自动赋值,无需手动处理
CreateInBatches 当成“全自动安全批量工具”——它不跳过脏数据、不聚合错误、不跨库一致、也不自动适配目标数据库的边界限制。你得自己控制事务、分片逻辑、错误定位和主键使用策略。










