gorm自动时间戳仅对createdat和updatedat字段名默认生效,需为time.time类型且导出;其他字段名须显式加gorm标签才能触发自动更新。

CreatedAt/UpdatedAt 字段名不是必须的,但命名会影响自动行为
GORM 的“自动时间戳”不是靠魔法识别字段语义,而是依赖约定字段名或显式标签。如果你把字段命名为 created_at 或 updated,GORM 默认不会管它——除非你加了对应标签。
默认生效的只有两个名字:CreatedAt 和 UpdatedAt(首字母大写,Go 结构体导出要求)。它们必须是 time.Time 类型(或指针),且不能被禁写(即不能有 gorm:")。
- 想用小写下划线命名?可以,但得加标签:
Created time.Time `gorm:"autoCreateTime;column:created_at"` - 想存毫秒时间戳?用
int64+ 标签:CreatedMs int64 `gorm:"autoCreateTime:milli"` - 想跳过某次更新(比如只改状态不更新时间)?用
db.Omit("UpdatedAt").Updates(...)
Save() 方法绕过所有钩子,时间戳不会更新
Save() 是 GORM 中唯一默认跳过 BeforeCreate/BeforeUpdate 钩子的写入方法。它设计目标就是“全量覆盖”,连零值都写,自然也不走时间赋值逻辑。
常见错误现象:调用 db.Save(&user) 后 UpdatedAt 没变,甚至倒退回零值。
- ✅ 正确做法:用
db.Updates(&user)或db.Model(&user).Updates(map[string]interface{}{"name": "x"}) - ✅ 单字段更新也安全:
db.Model(&user).Update("status", "active") - ❌ 绝对避免:
db.Save(&user),除非你明确要跳过时间逻辑
软删除时 UpdatedAt 不更新是正常行为,需手动干预
调用 db.Delete(&user) 会设置 DeletedAt,但 GORM 不会碰 UpdatedAt——这不是 bug,是设计如此。因为“软删除”本质是更新,但 GORM 认为这是独立生命周期事件。
如果你希望软删除也刷新最后操作时间,必须在 BeforeDelete 钩子里显式赋值:
func (u *User) BeforeDelete(tx *gorm.DB) error {
now := time.Now()
u.UpdatedAt = &now
return nil
}
- 注意:此时
u.DeletedAt已被 GORM 写入,但u.UpdatedAt还是旧值,可安全覆盖 - 如果用了
gorm.Model嵌入,UpdatedAt是非指针类型,记得改成*time.Time,否则赋值无效
字段类型必须是指针,否则钩子赋值静默失败
这是最隐蔽的坑:定义 UpdatedAt time.Time(非指针),然后在 BeforeUpdate 里写 u.UpdatedAt = time.Now(),看起来没问题,实际数据库里永远不变。
原因:GORM 在执行钩子前会复制结构体副本,你改的是副本里的值,原对象不受影响。
- ✅ 正确声明:
UpdatedAt *time.Time(配合钩子里u.UpdatedAt = &now) - ✅ 或直接用 GORM 内置别名:
gorm.DeletedAt是*time.Time,同理可自定义UpdatedAt *time.Time - ⚠️ 注意:MySQL 的
DATETIME类型能存NULL,但time.Time零值是0001-01-01 00:00:00 +0000 UTC,容易误判











