createinbatches 是 gorm 批量插入首选,因其官方封装、带事务与分批逻辑,自动切片、复用预编译语句,避免循环插入开销;建议批次设为100–500,注意主键回填限制及事务显式控制。

为什么 CreateInBatches 是 GORM 批量插入的首选
因为它是 GORM 官方封装的、带事务控制和分批逻辑的批量写入方法,不是简单循环 Create,也不是裸写原生 SQL。它能自动切片、复用预编译语句、避免单条插入的网络和事务开销。
常见错误是直接用 db.Create(&user) 循环插入 1000 条——这会发 1000 次请求,还可能触发连接池耗尽或超时;更隐蔽的问题是误以为 db.Create(&users)(传切片)就能批量插入,其实 GORM v2 默认仍是一条一条插(除非开启 AllowGlobalUpdate 并配合特殊写法,但不推荐)。
-
CreateInBatches第二个参数是每批数量,建议设为 100–500:太小起不到批量效果,太大可能触发 MySQL 的max_allowed_packet限制或内存压力 - 它内部使用
INSERT INTO ... VALUES (...), (...), (...)单条多值语句,兼容 MySQL/PostgreSQL/SQLite,但不支持 SQLite 的 RETURNING 子句(所以主键回填在 SQLite 下不可靠) - 若结构体含
CreatedAt/UpdatedAt等 GORM 标签字段,它们会在每条记录插入前自动赋值,无需手动设置
GORM 批量插入时主键没回填?检查 Returning 和数据库类型
MySQL 不支持 RETURNING,所以即使你写了 db.Clauses(clause.Returning{}).CreateInBatches(...),ID 字段也不会被自动写回切片中的 struct。PostgreSQL 支持,但仅限单条或明确指定字段,且 CreateInBatches 对 Returning 的支持较弱——v1.23+ 才开始实验性支持,v2.2+ 仍不稳定。
典型现象:插入后打印 users[0].ID 还是 0,但查库发现已写入。
- MySQL 场景下,如需主键回填,必须改用
db.Session(&gorm.Session{PrepareStmt: true}).CreateInBatches(...)+ 手动执行SELECT LAST_INSERT_ID()配合行数推算(不推荐),或接受“不回填”,后续用其他字段查 - PostgreSQL 可尝试
db.Clauses(clause.Returning{Columns: []clause.Column{{Name: "id"}}}).CreateInBatches(...),但注意:只对单批有效,多批时返回结果会错位 - 别依赖
db.CreateInBatches(...).RowsAffected判断是否成功——它返回的是总行数,不是成功数;出错时可能部分成功、部分失败,GORM 默认不回滚整批(除非显式开事务)
怎么用事务包住 CreateInBatches 避免脏数据
默认情况下 CreateInBatches 不开启事务,某一批失败,前面批次已提交,导致数据不一致。尤其在迁移脚本或后台任务中,这点极易被忽略。
正确做法是显式套一层事务,并捕获错误做统一回滚。
tx := db.Begin()
if err := tx.CreateInBatches(users, 100).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
- 别用
db.Transaction(func(tx *gorm.DB) error {...})包CreateInBatches:虽然语法简洁,但一旦中间某批失败,GORM 不会自动中断后续批次,而是继续执行并累积错误,最终事务可能部分写入 - 如果批次内某条记录违反唯一约束,整个批次会报错(MySQL 的 multi-value insert 是原子性的),但事务外的其他批次不受影响——所以分批大小也影响错误粒度
- 高并发写同一张表时,事务持续时间越长,锁等待风险越高;1000 条分 10 批比 1 批更安全,但也要权衡网络往返次数
替代方案:Exec 原生 SQL 批量插入的适用边界
当 CreateInBatches 无法满足需求时——比如要插入计算字段、跳过 GORM 钩子、或拼接动态列——就得退到原生 SQL。但别一上来就写 db.Exec("INSERT INTO ..."),容易 SQL 注入或类型错配。
最稳妥的方式是用 GORM 的 db.Statement.BindVars 机制生成安全占位符,再拼 SQL。
- 用
db.Session(&gorm.Session{DryRun: true}).Create(&user)查看 GORM 生成的 INSERT 模板,复制其字段顺序和占位符风格(如?或$1) - 手拼 SQL 时,所有值必须通过
db.Exec(sql, args...)传参,严禁字符串拼接变量,尤其是用户输入字段 - SQLite 下原生批量插入性能反而不如
CreateInBatches,因为其 WAL 模式对多值 insert 优化有限;而 PostgreSQL 的COPY接口(非 SQL)才是真高性能方案,但 GORM 不内置支持
真正难的不是写对语法,是判断该不该脱离 GORM 抽象层——多数业务场景里,调优 CreateInBatches 的批次大小、加索引、关掉钩子(db.Session(&gorm.Session{SkipHooks: true})),比手写 SQL 更快见效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











