最常见原因是字段未导出(首字母小写)或已手动赋非零值;gorm仅对导出的createdat/updatedat字段(如time.time类型)自动填充,且跳过已设非零值,需确保dsn含parsetime=true&loc=local,避免手动预设时间。

CreatedAt 和 UpdatedAt 字段为什么没自动赋值?
最常见原因是字段未导出(首字母小写),或结构体初始化时手动设置了非零时间值。GORM 只对导出字段(如 CreatedAt time.Time)触发自动填充,且默认跳过已设非零值——比如你写了 u.CreatedAt = time.Now() 再调用 Create,BeforeCreate 就不会覆盖它。
实操建议:
- 字段名必须首字母大写,类型推荐
time.Time(不是createdAt或*time.Time除非你明确需要空值语义) - 连接 DSN 中必须包含
parseTime=true&loc=Local,否则 MySQL 的 DATETIME 值会被当成[]byte,导致赋值失败 - 避免在创建前给
CreatedAt赋非零值;若需支持“零值即未设置”,才用*time.Time并配指针接收者回调
用 gorm.Model 嵌入还是自定义字段 + 回调?
嵌入 gorm.Model 最省事,但灵活性差:它强制使用 CreatedAt/UpdatedAt 字段名、固定为 time.Time 类型、且每次 Update(包括软删除)都会刷新 UpdatedAt。
实操建议:
- 新项目且无特殊需求,直接嵌入:
type User struct { gorm.Model Name string } - 要改字段名(如
created_at_ms)、换类型(如int64毫秒戳)、或跳过某些更新(如仅业务更新才改时间),就别嵌入,自己定义字段 +BeforeCreate/BeforeUpdate回调 - 嵌入后不能再定义同名字段,否则编译报错;若已有表结构含
create_time,必须用自定义方案
autoCreateTime 和 autoUpdateTime 标签怎么用?
这是 GORM 提供的声明式替代方案,比回调更轻量,适合不需要额外逻辑的场景。它不依赖回调注册,只要字段带对应 tag,GORM 在生成 SQL 时就会自动注入当前时间。
实操建议:
- 字段名可任意,但必须加 tag:
Created time.Time `gorm:"autoCreateTime"`、Updated time.Time `gorm:"autoUpdateTime"` - 支持精度控制:
Updated int64 `gorm:"autoUpdateTime:nano"`(纳秒)、Created int64 `gorm:"autoCreateTime:milli"`(毫秒) - 注意:该机制只在传结构体时生效(如
Create(&u)),传map[string]interface{}或用Updates()不触发
数据库 default 和 GORM 自动填充冲突怎么办?
MySQL 表定义了 CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,但 GORM 还试图写 UpdatedAt,容易导致时间不一致或主从延迟放大。
实操建议:
- 优先让数据库负责
updated_at,GORM 只管created_at:给UpdatedAt加禁止写入,同时确保表结构有 <code>ON UPDATE CURRENT_TIMESTAMP - 若必须由 GORM 控制,就关掉数据库侧的自动更新,并统一用回调或
autoUpdateTime标签 - 两者混用时,务必确认时区:数据库
SET time_zone = '+08:00',Go 连接参数用loc=Asia/Shanghai,否则可能差 8 小时
真正容易被忽略的是:自动填充只在结构体操作时有效,db.Table("users").Where(...).Updates(map[string]interface{}{"name": "x"}) 这类 map 更新完全绕过所有 GORM 时间逻辑。别指望它能“智能补时间”。











