Updates适合多字段/多记录批量更新但默认忽略零值,Update专用于单字段精准更新;必须带Where条件,零值更新需用map或Select指定。

用 Updates 还是 Update?先看场景再选方法
很多人一上来就用 Updates,结果发现零值字段没更新、条件漏写、甚至全表被误改。GORM 的批量更新不是“越快越好”,而是“越准越稳”。Update 适合单字段+明确主键或条件的精准更新;Updates 才是真正面向多字段、多记录的批量操作入口,但它默认跳过零值(比如 Age: 0、Email: ""),这点极易踩坑。
常见错误现象:db.Model(&User{}).Where("status = ?", "pending").Updates(User{Status: "done", Age: 0}) 只更新了 Status,Age 被忽略——因为 0 是零值,GORM 默认不处理。
- 想强制更新零值字段?必须传
map[string]interface{}:Updates(map[string]interface{}{"age": 0, "status": "done"}) - 想用 struct 但保留零值?得配合
Select显式指定字段:Updates(user).Select("age", "status") - 绝对不要省略
Where——否则会触发ErrMissingWhereClause错误,GORM 不允许无条件全表更新
Exec 直接执行原生 SQL 是最快的方式吗?
是,但代价是失去 GORM 的类型安全、钩子(BeforeUpdate 等)、软删除和字段自动填充(如 UpdatedAt)。如果你只需要纯性能、且字段逻辑简单,Exec 确实最直接:
db.Exec("UPDATE users SET status = ?, updated_at = ? WHERE status = ?", "done", time.Now(), "pending")
但要注意:它绕过了所有 GORM 中间层,updated_at 不会自动更新(除非你手动写进去),事务也得自己控制,且参数绑定需严格匹配顺序。
- 适合场景:后台定时任务、数据迁移、紧急修复等无需业务钩子的场景
- 不推荐用于核心业务逻辑,尤其涉及审计字段、状态机流转或关联更新时
- MySQL 下注意:如果表有触发器,
Exec仍会触发;但 GORM 钩子完全不运行
为什么 Save 不该用于批量更新?
Save 看似万能,但它本质是“全字段覆盖式更新”——无论你改没改某个字段,只要结构体里有值(包括零值),就会写进数据库。更关键的是,Save 必须基于已有实体实例,意味着你要先 Find 出来,这一步在批量场景下就是性能杀手。
典型反模式:db.Find(&users).Save(&users),它会:① 全量查出所有记录 → ② 在内存中逐个赋值 → ③ 逐条执行 UPDATE(即使你只改一个字段)→ ④ 每条都更新所有字段(含未变更字段)。
- 后果:网络往返多、内存占用高、锁持有时间长、容易被其他事务阻塞
- 例外情况:只有极少数记录(BeforeSave 或依赖模型验证时才考虑
Save - 替代方案:用
Updates+Where+map组合,既高效又可控
批量更新时 UpdatedAt 字段怎么自动生效?
GORM 默认只在 Update 和 Updates 中自动更新 UpdatedAt(前提是模型里定义了该字段且类型为 time.Time 或 sql.NullTime)。但有个隐藏前提:你不能用 map[string]interface{} 方式绕过它——一旦用 map,GORM 就认为你是“完全自控”,不再注入时间戳。
所以,如果你需要自动 UpdatedAt,又想更新零值字段,正确姿势是:
db.Model(&User{}).Where("status = ?", "pending").
Updates(map[string]interface{}{"status": "done", "age": 0}).
Omit("updated_at") // ❌ 错误:Omit 会禁用自动更新
应该改成:
db.Model(&User{}).Where("status = ?", "pending").
Select("status", "age", "updated_at"). // ✅ 显式包含 updated_at,GORM 会自动填入当前时间
Updates(User{Status: "done", Age: 0})
- 要点:用
Select列出要更新的字段(含updated_at),GORM 才会在生成 SQL 时自动注入now()类函数 - 别信文档里“自动生效”的模糊描述——实际是否生效,取决于你传参方式和
Select是否显式包含该字段 - 如果用了
Exec或第三方驱动(如pgx),UpdatedAt完全不会自动工作,必须手写
UpdatedAt 自动填充机制高度耦合,且行为随传参方式突变**。写之前务必确认你用的是 struct 还是 map,是否显式 Select,以及是否真需要钩子——选错一个,就可能从“高效批量”退化成“隐蔽慢查询”。


















