GORM 批量插入报错怎么解决?占位符上限、Duplicate entry 冲突与 CreateInBatches 实战全攻略
报错现场
批量插入是后台系统的家常便饭:导入 Excel、同步上游数据、刷历史订单。但一到 GORM 批量操作,报错花样百出。本文按出现频率排序,给出五大典型报错的成因和可直接套用的修复代码,环境为 GORM v2(gorm.io/gorm)+ MySQL。
报错一:invalid value, should be slice
gorm: invalid value, should be slice
成因:Create 想要的是切片,你传了单个结构体或指针的指针。常见误传:Create(&user)(单个)、Create(users)(切片但没取地址,值为 nil 切片时也报这个)、Create(&[]*User{...}) 里混了 nil 元素。
修复:
users := []User{{Name: "a"}, {Name: "b"}}
if err := db.Create(&users).Error; err != nil { // 注意取地址,传切片
log.Fatal(err)
}
for i := range users {
fmt.Println(users[i].ID) // 批量插入后自增 ID 会回填
}
报错二:Error 1390: Prepared statement contains too many placeholders
Error 1390 (HY000): Prepared statement contains too many placeholders
成因:MySQL 单条 SQL 的占位符上限是 65535(16 位计数器)。你一次 Create(&users) 塞了 2 万条、每条 10 个字段,占位符 = 20000 × 10,直接爆表。GORM 默认不分批,切片多大拼多大。
修复:用 CreateInBatches 手动分批,批次大小按「列数 × 批次 ≤ 65535」倒推,并留余量:
// 假设 User 有 8 个字段,每批 2000 条 → 16000 个占位符,安全
if err := db.CreateInBatches(&users, 2000).Error; err != nil {
log.Fatal(err)
}
字段越多批次越小;30 个字段的宽表建议每批 500 以内。这也顺带缓解了报错四的 packet 问题。
报错三:Error 1062: Duplicate entry … for key ‘xxx’
Error 1062 (23000): Duplicate entry 'a@example.com' for key 'users.email'
成因:批量数据里有重复主键/唯一键,或者和库里已有数据冲突,而表上有唯一索引。GORM 不会自动忽略,默认直接整体报错回滚。
修复:按业务语义选一种 clause.OnConflict 策略(GORM v2 统一入口,MySQL 底层翻译为 ON DUPLICATE KEY UPDATE):
import "gorm.io/gorm/clause"
// 策略 A:冲突则忽略(导入数据求快不求全时最常用)
db.Clauses(clause.OnConflict{DoNothing: true}).CreateInBatches(&users, 1000)
// 策略 B:冲突则更新指定列(同步上游数据的幂等写法)
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}}, // 以哪个唯一键判断冲突
DoUpdates: clause.AssignmentColumns([]string{"name", "updated_at"}),
}).CreateInBatches(&users, 1000)
注意:Columns 里指定的必须是数据库里真实存在的唯一索引(gorm:"uniqueIndex" 建出来的或手工建的),否则 MySQL 不知道该按什么判冲突。另外 OnConflict 只影响有冲突的行,批次里其余行正常插入。
报错四:Error 1366: Incorrect string value
Error 1366 (HY000): Incorrect string value: '\xF0\x9F\x98\x80' for column 'name'
成因:emoji 或生僻字进来了,但表/列是 utf8(MySQL 里的 utf8 实际是阉割版 utf8mb3,最多 3 字节),4 字节的 UTF-8 字符塞不进。
修复:库、表、列、连接四层统一成 utf8mb4:
ALTER DATABASE yourdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
// DSN 里显式声明字符集,防止连接层掉回默认
dsn := "user:pass@tcp(127.0.0.1:3306)/yourdb?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
顺带一提,如果修复后出现 Error 1071: Specified key was too long,那是索引列超过 utf8mb4 下的 767 字节老限制,给索引列限长或确认 MySQL ≥ 5.7 并使用 DYNAMIC 行格式即可。
报错五:Error 1153: Got a packet bigger than ‘max_allowed_packet’
Error 1153 (08S01): Got a packet bigger than 'max_allowed_packet' bytes
成因:批次拼出来的 SQL 包超过 MySQL 的 max_allowed_packet(默认常见 4M/16M/64M,取决于版本与配置)。字段里有长文本(JSON、文章内容)时尤其容易踩。
修复:优先调小批次;确实需要大包时调大服务端配置:
-- 查看当前值
SHOW VARIABLES LIKE 'max_allowed_packet';
-- 会话级临时调大(永久生效需改 my.cnf 后重启)
SET GLOBAL max_allowed_packet = 64 * 1024 * 1024;
附:批量插入性能速查清单
| 手段 | 说明 |
|---|---|
CreateInBatches(&data, N) |
手动分批,N 按列数倒推占位符 |
| 每批一条事务 | GORM 的 CreateInBatches 默认整批一个事务,失败整批回滚;想「跳过坏行」就自己循环分批并逐批处理错误 |
| 关闭默认事务提速 | SkipDefaultTransaction: true,确认能接受非原子性后用于纯导入 |
| 钩子副作用 | BeforeCreate 等钩子对每批每行都会触发,批量场景注意性能 |
| 去重前置 | 导入前在应用层用 map 去重,比依赖数据库报错重试快得多 |
版本说明
- 本文基于 gorm.io/gorm v1.31.2(2026-11-02 核对,GitHub releases 最新 tag v1.31.2,发布于 2026-06-25);安装命令
go get gorm.io/gorm@v1.31.2。 - MySQL 驱动 gorm.io/driver/mysql v1.6.0(2026-11-02 核对,github.com/go-gorm/mysql tag 列表最新 tag v1.6.0);安装命令
go get gorm.io/driver/mysql@v1.6.0。 - 文中
CreateInBatches、clause.OnConflict{DoNothing / Columns / DoUpdates}等 API 均为 GORM v2 稳定接口,对照官方文档(gorm.io/docs/create.html)核实。 - 涉及的 MySQL 错误码(1390/1062/1366/1153/1071)为 MySQL 长期稳定错误定义,与具体小版本无关。
相关阅读:《GORM CRUD 基础操作》、《GORM 性能优化》、《GORM 常见问题与最佳实践》、《GORM 事务处理》。
- 《Go 程序设计语言》(Go 语言圣经,机械工业出版社) —— Go 官方团队出品,公认入门第一书
- 《Go 语言实战》(异步图书,人民邮电出版社) —— 实战导向,面试前巩固工程手感
- 《Go 语言编程之旅:一起用 Go 做项目》(博文视点,电子工业出版社) —— 项目驱动入门,从零写出完整应用
- Keychron K1Max 矮轴机械键盘(Mac/Win 双系统,QMK/VIA 开源改键) —— 轻薄矮轴 + 开源固件,程序员桌面常客
- 罗技 MX Master 3S 无线鼠标(Flow 跨屏,静音电磁滚轮) —— 人体工学设计,写代码一整天手不酸
以上链接含京东联盟推广代码;通过链接购买本站可能获得少量佣金,不影响你的购买价格。