CreateInBatches是GORM唯一推荐的批量插入方法,因其拼接单条多值INSERT语句,显著减少网络往返、SQL解析和连接争用;但需手动设batchSize(MySQL 100–500,PG 50–100)、显式事务、注意主键不回填及钩子失效。

为什么 CreateInBatches 是批量插入的首选方法
直接用循环调用 Create 插入 10 万条数据,会触发 10 万次 SQL 执行,网络往返和事务开销极大,基本不可行。GORM 提供的 CreateInBatches 是唯一真正可控、可设批次大小的原生批量方案——它底层拼接单条 INSERT INTO ... VALUES (...), (...), (...) 语句,复用连接,避免 ORM 层反复解析模型。
注意:不是所有数据库都支持无限长的 VALUES 列表。PostgreSQL 默认限制约 65k 参数,MySQL 受 max_allowed_packet 和 group_concat_max_len 影响,SQLite 更是容易触发 SQLITE_TOOBIG。所以必须显式控制批次大小。
实操建议:
- 始终传入非零的
batchSize参数,例如db.CreateInBatches(records, 1000) - PostgreSQL 用户建议 ≤ 500;MySQL 建议 ≤ 2000(需确认服务端配置)
- 不要依赖 GORM 自动分批——它不会帮你拆解超长切片,只按你给的
batchSize切
CreateInBatches 的坑:事务、主键、钩子全失效
这个方法绕过 GORM 正常生命周期,BeforeCreate/AfterCreate 钩子不执行,gorm.Model(&u).Create(&u) 类的自定义逻辑也无效。更重要的是:它默认不开启事务,也不会自动填充主键(如 PostgreSQL 的 SERIAL 或 MySQL 的 AUTO_INCREMENT),返回的 records 中 ID 字段仍是零值。
常见错误现象:records[0].ID == 0 却误以为插入失败;或在钩子里做审计日志,结果日志全漏了。
使用场景判断:
- 需要主键回填 → 改用原生 SQL +
RETURNING(PostgreSQL)或LAST_INSERT_ID()(MySQL),别碰CreateInBatches - 必须跑钩子 → 老老实实单条
Create+ 手动事务封装,或改用Session控制,但性能必然下降 - 纯日志/埋点类写入 →
CreateInBatches完全适用,关掉钩子反而更干净
原生 SQL 批量插入:绕过 GORM 解析开销
当数据量超过 50 万、字段少、结构固定时,手写 SQL 拼接比 CreateInBatches 快 3–5 倍。GORM 的 struct → map → SQL 参数映射过程有明显 CPU 开销。
实操建议:
- 用
db.Exec("INSERT INTO users (name, age) VALUES (?, ?), (?, ?)", args...),args 是扁平化参数切片 - 避免字符串拼接字段名,但 VALUES 中的问号可以批量展开(GORM 支持多值占位符)
- PostgreSQL 强烈推荐加
ON CONFLICT DO NOTHING防重复,MySQL 用INSERT IGNORE或ON DUPLICATE KEY UPDATE - 注意参数总数不能超驱动限制(
lib/pq默认 65535,go-sql-driver/mysql受max_allowed_packet约束)
超大数据量(千万级)必须考虑的三件事
到这个量级,GORM 已不是瓶颈,数据库和 I/O 成为关键。单纯调大 batchSize 可能导致 OOM 或连接超时。
必须检查:
- 数据库是否启用了
LOAD DATA INFILE(MySQL)或COPY FROM(PostgreSQL)——它们比任何 ORM 批量都快一个数量级 - GORM 连接池是否足够:
db.DB().SetMaxOpenConns(100),否则并发插入会卡在获取连接 - 是否关闭了 GORM 日志:
db = db.Debug().LogMode(false),DEBUG 日志在百万级插入时会吃掉大量 CPU
如果业务允许短暂离线,pg_dump --data-only + psql -c "COPY..." 是最稳最快的路径。GORM 只适合在线、中小批量、带业务逻辑的场景。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











