Save会将结构体所有非零字段写入数据库,但零值(0、""、nil)被跳过,导致Age: 0或Name: ""无法更新;Updates传struct跳过零值,传map则强制更新所有key;安全更新需显式Where+map指定字段,避免直接Save未查询的结构体。

Save 会把零值写进数据库,不是“只更新改过的字段”
很多人以为 Save 是安全的“保存当前对象”,结果发现 Age: 0 或 Name: "" 被强制写入,覆盖了原来的真实值。这是因为 Save 默认更新结构体里所有非零字段——但“零值判断”只看 Go 类型默认值(0、""、nil),不区分“用户有意清空”还是“字段没赋值”。
- 想把
Name改成空字符串?Save不行,它会跳过"" - 想把
Age改成0?同理,Save直接忽略这个字段 - 更危险的是:如果结构体漏传字段(比如没查全就直接
Save),那些字段会被设为零值写入
Update 和 Updates 的零值行为完全相反
Update 和 Updates 默认跳过零值,但控制粒度不同:前者只能单字段,后者支持多字段,且传 map 和传 struct 行为不一致。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 传
struct给Updates:自动跳过所有零值字段(和Save类似,但不会插入) - 传
map[string]interface{}给Updates:所有 key 对应字段都会更新,包括"age": 0 -
Update("name", "xxx"):明确指定字段,nil或0都能写入(需配合Where或主键)
Where + Updates 是最可控的批量更新组合
直接对未查询的结构体调用 Save 或 Updates 极易出错。真正安全的做法是显式构造条件,再更新目标字段。
- 更新单条:用
db.Where("id = ?", 123).Updates(map[string]interface{}{"name": "Alice", "age": 0}) - 更新多条:
db.Where("status = ?", "pending").Updates(map[string]interface{}{"status": "processing"}) - 想更新零值又怕误伤?加
Select显式限定字段:db.Model(&u).Select("age", "email").Updates(u) - 绝对要避免:
db.Save(&u)前没做First查询,尤其当u.ID来自前端输入时
UpdateColumn 绕过 GORM 安全机制,慎用
UpdateColumn 和 UpdateColumns 不触发钩子、不校验零值、不走默认更新逻辑——它就是裸 SQL 更新,性能高但风险也高。
- 适合场景:后台定时任务批量修正某个字段(如
updated_at批量刷时间) - 不适合场景:业务逻辑中的常规更新,尤其是涉及关联校验或审计日志时
- 注意:
UpdateColumn("age", 0)会真的把age设为0,但不会更新updated_at(除非你手动写进去)
Save 不是“保存修改”,而是“UPSERT”;Updates 传 struct 不等于“更新所有字段”。关键永远在:你想更新什么、哪些值允许为零、是否需要钩子、有没有并发风险——这些决定了该敲哪一行代码。

















