CreateInBatches非自动批量,需设对批次大小、显式事务、主键回填方案;batchSize依数据库调优(MySQL 100–500,PG 50–100,SQLite 1000);不触发钩子,ID不自动回填;万级数据建议原生批量。

直接用 CreateInBatches,但别只传个切片就跑——批次大小、事务控制、主键回填这三处不设对,10 万条数据可能静默失败一半。
为什么 CreateInBatches 不等于“自动批量”
它只是把切片按指定数量切块,每块拼成一条 INSERT INTO ... VALUES (), (), () 发出去。不是魔法,更不是事务兜底:
- batchSize 设为 1 就退化成循环
Create,还多一层函数调用开销 - 不显式开事务时,某一批失败,前面已成功的批次不会回滚
- 空切片安全,但传
*[]User或单个 struct 会 panic - 返回值是
*gorm.DB,不是error;必须检查result.Error != nil
CreateInBatches 的 batchSize 怎么设才不翻车
不是越大越好,得看数据库和字段复杂度:
- MySQL:建议 100–500,超 1000 易触发
Packets larger than max_allowed_packet - PostgreSQL:50–100 更稳,单语句行数过多会拖慢 WAL 写入、触发服务端内存压力
- SQLite:可稍高(如 1000),但必须提前
PRAGMA journal_mode = WAL,否则写入卡死 - 别靠经验拍脑袋——实测时用
EXPLAIN ANALYZE(PG)或SHOW PROFILE(MySQL)看单批耗时拐点
需要插入后拿到 ID?别指望 CreateInBatches 自动回填
这是 GORM 的明确设计取舍:为兼容 SQLite 和性能,默认跳过结果扫描。现象就是 users[0].ID 还是 0,但库里已存在。
立即学习“go语言免费学习笔记(深入)”;
- PostgreSQL 用户可试
db.Clauses(clause.Returning{Columns: []clause.Column{{Name: "id"}}}).CreateInBatches(),但仅限单批有效,多批时返回错位风险高 - MySQL 基本无解:
LAST_INSERT_ID()只返回第一条 ID,后续无法映射 - 真要 ID 可靠:插入前用
uuid.NewString()或 Snowflake 生成,彻底绕开自增回填 - 钩子函数(
BeforeCreate)在CreateInBatches中完全不触发——预处理必须在调用前做完
万级数据别只靠 GORM,该切原生就切
当数据量上到 10 万+ 或字段含 TEXT/JSON/BLOB,GORM 的封装成本开始反噬性能:
- PostgreSQL 场景:用
pgx.Conn.CopyFrom,比CreateInBatches快 5–10 倍,但要求数据已转成[][]interface{},列顺序、类型、nil 处理必须严丝合缝 - MySQL 场景:手动拼
INSERT多值语句 +db.Prepare,注意占位符总数不能超 65535,需按len(data) * fieldCount主动分片 - Neo4j 场景:用
UNWIND $data AS props CREATE (n:Label) SET n = props,单次请求千节点,别起 goroutine 并发发CREATE
最易被忽略的点:所有批量操作前,必须用 db.Begin() 包事务——否则万次自动提交,不是慢在 Go,是慢在数据库反复刷盘。


















