GORM 默认自动更新 UpdatedAt 字段需满足:字段名严格为 UpdatedAt、类型为 time.Time、未在操作中手动传入;批量更新不触发自动更新,须手动指定。

如何让 GORM 自动更新 UpdatedAt 字段
GORM 默认会对名为 UpdatedAt 的 time.Time 类型字段自动更新(只要该字段存在且未被显式赋值)。但这个行为依赖几个关键前提,漏掉任何一个都会导致“不更新”。
常见错误现象:UpdatedAt 始终是零值、或只在创建时更新、或完全没变化。
- 结构体字段名必须严格为
UpdatedAt(首字母大写,大小写敏感) - 类型必须是
time.Time或其别名(如sql.NullTime不触发自动更新) - 不能在
Save()或Updates()时手动传入UpdatedAt字段(哪怕传nil或零值,GORM 会跳过自动逻辑) - 使用
Update()单字段更新时,UpdatedAt不会更新——这是设计如此,不是 bug
示例:
type User struct {
ID uint `gorm:"primaryKey"`
Name string
UpdatedAt time.Time `gorm:"index"` // ✅ 正确:字段名+类型匹配
}
自定义时间戳字段名(比如叫 updated_at)
如果数据库字段是蛇形命名(updated_at),或想用其他名字(如 LastModified),就不能依赖默认行为,得显式配置。
立即学习“go语言免费学习笔记(深入)”;
用 GORM:UpdateTime 标签声明,并确保类型仍是 time.Time:
type Post struct {
ID uint `gorm:"primaryKey"`
Title string
LastModified time.Time `gorm:"column:updated_at;autoUpdateTime:true"`
}
注意:autoUpdateTime:true 是必需的;若设为 false,即使字段名是 UpdatedAt 也会禁用自动更新。
兼容性提示:GORM v2 中 autoUpdateTime 默认为 true,但显式写出更稳妥,尤其团队协作时避免歧义。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
CreatedAt 和 DeletedAt 的自动行为差异
CreatedAt 只在 Create() 时设置一次,后续任何更新都不再变动;DeletedAt 则只在调用 Delete() 软删除时自动赋值。
它们都遵循和 UpdatedAt 相同的命名与类型规则,但作用时机不同:
-
CreatedAt:仅首次插入生效,之后无论Save还是Updates都不覆盖 -
DeletedAt:仅当模型嵌入gorm.DeletedAt且调用db.Delete(&u)时写入,普通Updates不影响它 - 三者可共存,互不干扰
如果你发现 CreatedAt 在更新时变了,大概率是代码里误写了 u.CreatedAt = time.Now(),GORM 不会“抢修”你的手动赋值。
批量更新时 UpdatedAt 不生效?这是预期行为
用 db.Model(&User{}).Where("id > ?", 100).Updates(map[string]interface{}{"name": "foo"}) 这类批量操作时,UpdatedAt 绝对不会自动更新——GORM 不会为每条记录查出来再 Save,性能上也不合理。
如果业务强依赖批量更新也改时间戳,只能手动指定:
db.Model(&User{}).
Where("id > ?", 100).
Updates(map[string]interface{}{
"name": "foo",
"updated_at": time.Now(), // ✅ 手动覆盖,字段名按实际 DB 列名写
})
或者用原生 SQL:db.Exec("UPDATE users SET name = ?, updated_at = NOW() WHERE id > ?", "foo", 100)。
真正容易被忽略的是:很多人以为“用了 GORM 就万事大吉”,结果线上批量任务的时间戳几个月不动,排查时才发现是这个限制。

















