gorm.Model() 仅更新主键和显式设置字段以保留数据库默认值,直接 db.Create() 会写入所有字段包括零值;Create() 返回 *gorm.DB 需检查 err 和 RowsAffected;批量插入应使用 CreateInBatches() 而非循环 Create()。

gorm.Model() 和直接传结构体的区别
用 Create() 插入数据时,GORM 不会自动忽略零值字段(比如 0、""、false),除非你明确告诉它哪些字段不该写入数据库。很多人以为传个结构体就能“智能跳过空值”,结果整条记录的 int 字段全被写成 0,string 写成空字符串,甚至把原本设为 NOT NULL DEFAULT 'active' 的字段覆盖掉了。
常见错误现象:user.Status 原本是数据库默认值 'active',但 Go 结构体里没赋值,Create(&user) 后变成 '' 或 NULL(取决于字段是否允许为空)。
- 想保留数据库默认值 → 用
gorm.Model(&user).Create(&user),它只更新主键和显式设置的字段,其余跳过 - 想强制写入所有字段(包括零值)→ 直接
db.Create(&user) - 只想写特定字段 → 改用
db.Select("Name", "Email").Create(&user)
Create() 返回值里藏着关键信息
Create() 的返回值不是布尔或错误,而是 *gorm.DB,所以必须检查 err 字段才能知道是否成功。更关键的是:插入后主键是否回填,取决于结构体里主键字段有没有标签声明。
使用场景:你调了 db.Create(&user),接着打印 user.ID,却发现还是 0 —— 很可能是因为没加 gorm:"primaryKey" 或没用 gorm:"column:id" 对齐数据库字段名。
立即学习“go语言免费学习笔记(深入)”;
- 确保主键字段有
gorm:"primaryKey"标签(如ID uint `gorm:"primaryKey"`) - 如果数据库主键叫
user_id,结构体字段名是UserID,就得加gorm:"column:user_id" - 别依赖
db.Create().Error == nil就认为插入成功,还要看RowsAffected是否为1
批量插入用 CreateInBatches 而不是循环 Create
用 for 循环反复调 Create() 插 100 条数据,实际会发 100 次 SQL,性能极差,还容易触发连接池耗尽。GORM 提供了 CreateInBatches(),底层是单条 INSERT INTO ... VALUES (),(),(),效率高得多。
参数差异:CreateInBatches() 第二个参数是每批数量,不是总条数。设成 1000 并不意味着一次插满 1000 条 —— 如果总共只有 300 条,它就只发一批;但如果传入切片长度是 2500,它会发三批(1000+1000+500)。
- 别写
for _, u := range users { db.Create(&u) } - 改用
db.CreateInBatches(users, 100),100 是较安全的批量大小(适配 MySQL max_allowed_packet) - 注意:
CreateInBatches()不会回填每条记录的主键 ID(只回填第一个),如需全部 ID,得自己查或用事务 + RETURNING(PostgreSQL)
插入失败时的 error 类型容易误判
很多人只检查 err != nil,但 GORM 的 err 可能是 *mysql.MySQLError、*pq.Error 或普通 fmt.Errorf,不同驱动报错格式完全不同。比如 MySQL 报唯一键冲突是 ErrCode: 1062,而 PostgreSQL 是 SQLState() == "23505"。
性能影响:用 errors.Is(err, gorm.ErrRecordNotFound) 这类通用判断毫无意义 —— Create() 根本不会返回这个错误;它只在 First() 等查询方法中出现。
- 判断唯一约束冲突:MySQL 看
err.(*mysql.MySQLError).Number是否等于1062;PostgreSQL 看err.(*pq.Error).Code是否等于"23505" - 别用
strings.Contains(err.Error(), "duplicate")—— 生产环境日志可能被翻译或截断 - 如果用
db.Unscoped().Create()绕过软删除,记得确认表里真没有同名逻辑删除记录,否则照样报唯一冲突
最常被忽略的点:GORM 默认开启事务执行 Create(),但如果你在自定义事务里再套一层 Create(),出错时 rollback 的边界容易混乱。要么全用 db.Transaction() 显式控制,要么确保外层事务对象和 Create() 用的是同一个 *gorm.DB 实例。










