update方法只更新传入的单个字段,如db.model(&user).update("status", "active"),生成sql为update...set status=?,需确保user已查出且id非空,不支持批量更新。

Update 方法只更新传入的单个字段
当你明确只想改一个字段(比如只改 status 或 updated_at),Update 是最直接的选择。它不会碰结构体里其他字段,也不关心零值问题。
常见错误是误用 Save 或漏写 Model 链式调用,导致 SQL 生成失败或更新了不该动的字段。
-
db.Model(&user).Update("status", "active"):只生成UPDATE ... SET status = ?,安全可靠 - 必须带
Model(&user),否则 GORM 不知道要更新哪张表、用什么主键条件 - 如果
user没预先查出来(即 ID 为空),GORM 会报record not found错误 - 不支持批量更新——一次只能作用于一个已有记录
Updates 方法支持多字段 + map 或 struct 两种写法
Updates 是日常更新操作中最灵活的入口,能同时改多个字段,且天然适配部分更新场景。
容易踩的坑在于字段零值处理和 struct 传参时的“字段覆盖”行为。
- 用
map[string]interface{}最稳妥:db.Model(&user).Updates(map[string]interface{}{"name": "Alice", "age": 0})—— 零值(如0、"")会被写入数据库 - 用 struct 传参时,GORM 默认跳过零值:
db.Model(&user).Updates(User{Name: "Alice", Age: 0})中的Age: 0会被忽略(除非你显式启用AllowGlobalUpdate或用Omit控制) - 若想强制更新 struct 中的零值字段,加
Omit反而没用;正确做法是换用map,或改用Save - 注意:struct 传参会按字段名映射,但不会自动忽略未导出字段(首字母小写),这些字段会被静默丢弃
Save 方法会全量覆盖,但依赖完整结构体状态
Save 不是“指定字段”,而是“指定结构体”的全量写入。它适合你手头刚好有一个带 ID 的完整对象,且希望把所有字段(含零值)都刷进数据库。
但它隐含强约束:对象必须已从 DB 查出(或至少 ID 已知),否则可能插入新记录而非更新。
-
db.Save(&user)生成的 SQL 是UPDATE users SET name=?, age=?, updated_at=? WHERE id = ?,所有非 tag 忽略字段都会参与 - 如果
user.Age是0,它真会写age = 0到数据库,这点和Updates(struct{})行为相反 - 并发下风险高:A 和 B 同时查出同一条记录,各自改不同字段再
Save,后保存者会覆盖前者的修改(丢失更新) - 不推荐用于 API 接收的 patch 请求——因为客户端未必传全字段,容易把未传字段置零
动态字段更新要用 map[string]interface{} + Where 组合
当字段名来自请求参数(比如前端传了 {"name": "Tom", "email": "t@x.com"}),硬编码字段名就不可行,必须走动态路径。
这时候不能依赖结构体,map[string]interface{} 是唯一干净解法,配合 Where 精确控制范围。
- 典型模式:
db.Model(&User{}).Where("id = ?", id).Updates(updateMap)—— 注意这里Model(&User{})是空结构体,仅用于指定表名和类型 - 避免用
db.Table("users").Where(...).Updates(...),它绕过 GORM 的字段映射和 hook,后续加软删除、时间戳等逻辑会失效 - 如果 updateMap 中 key 不是合法列名(比如带空格或 SQL 注入字符),GORM 不校验,需业务层清洗
- 性能上无明显差异,但比反射拼 SQL 更安全、更易测试
Update、Updates、Save 的签名差异里,而不是文档末尾的备注中。











