createinbatches是唯一靠谱的批量插入方式,因gorm v2中db.create(&users)仍逐条执行insert,仅createinbatches生成单条多值sql,大幅降低开销;批次宜设100–500,需手动事务保障原子性,主键回填依赖数据库能力。

为什么CreateInBatches是唯一靠谱的批量插入方式
因为GORM v2默认不把切片当批量处理——db.Create(&users)(users是[]User)仍会逐条执行INSERT,和循环db.Create(&u)没本质区别。真正起效的只有CreateInBatches,它底层生成INSERT INTO ... VALUES (?, ?), (?, ?), ...单条多值语句,网络往返、SQL解析、事务开销直接砍掉90%以上。
常见误操作包括:
– 传切片给Create就以为批量了
– 开启AllowGlobalUpdate强行让Create支持切片(不安全,易误删)
– 用db.Transaction包住循环Create(仍是N次请求,只是共用一个事务)
CreateInBatches的批次大小怎么设才不翻车
设为100–500之间最稳妥。太小(如10)无法摊薄连接/解析开销;太大(如2000)容易触发MySQL的max_allowed_packet限制(默认4MB),尤其字段含TEXT或BLOB时。
- 估算公式:
单行平均字节数 × 批次大小 (留1MB缓冲) - PostgreSQL对大批次更宽容,但超过1000仍可能OOM
- SQLite在
CreateInBatches下不支持RETURNING,主键回填不可靠,慎用于需要ID立刻可用的场景
事务必须手动包,否则数据不一致是常态
CreateInBatches默认不开启事务。如果插入1000条分10批(每批100),第6批失败,前5批已提交,后4批没执行——这是线上事故高发点,尤其在迁移脚本或定时任务里。
正确写法:
tx := db.Begin()
err := tx.CreateInBatches(users, 100).Error
if err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
注意:
– 不要用db.Session(&gorm.Session{NewDB: true})替代事务,它不保证原子性
– 微服务中若上游已开启事务,嵌套Begin()可能被忽略,需检查tx.Statement.ConnPool是否复用同一连接
主键回填失败?先看数据库类型再动手
插入后users[0].ID还是0,不是代码写错了,是数据库能力限制:
- MySQL完全不支持
RETURNING,db.Clauses(clause.Returning{}).CreateInBatches()无效 - PostgreSQL支持,但仅限单批;多批时
clause.Returning返回结果会错位,别信RowsAffected值 - 想拿ID,MySQL只能接受“插入后查”或改用
LAST_INSERT_ID()+ 行数推算(不推荐)
真正难处理的不是性能,是主键依赖逻辑混在业务流里——比如插入用户后立刻发通知,这时得提前设计ID生成策略(snowflake、UUID),别卡在GORM回填上。











