不能直接用 time.now() 做数据库时间比对,因为应用与数据库服务器时钟不同步会导致订单超时、缓存过期等逻辑错误,且丢失时区语义和事务一致性;应使用 db.raw() 调用数据库原生时间函数(如 mysql 的 now()、postgresql 的 current_timestamp)或配置 gorm 的 nowfunc 统一使用数据库时间。

为什么不能直接用 time.Now() 做数据库时间比对
因为应用服务器和数据库服务器时钟不同步是常态,哪怕只有几百毫秒偏差,在订单超时、缓存过期、日志排序等场景下就会引发逻辑错误。用 time.Now() 生成的条件(比如 WHERE created_at > '2026-08-18 22:30:00')本质是把本地时间“硬编码”进 SQL,失去数据库时区语义和事务一致性保证。
用 db.Raw() 调用数据库原生 NOW() 函数
最直接可靠的方式是让数据库自己报时,避免任何客户端参与:
- MySQL:用
db.Raw("NOW()")或更精确的db.Raw("CURRENT_TIMESTAMP(3)")(带毫秒) - PostgreSQL:
db.Raw("CURRENT_TIMESTAMP")或db.Raw("NOW()")都可,推荐前者 - SQLite:
db.Raw("datetime('now')")(无毫秒),或db.Raw("strftime('%Y-%m-%d %H:%M:%f', 'now')")(含毫秒)
示例:查 5 分钟内创建的用户
var users []User
db.Where("created_at > ?", db.Raw("DATE_SUB(NOW(), INTERVAL 5 MINUTE)")).Find(&users)
注意:db.Raw() 返回的是表达式对象,不能直接赋值给 Go 变量;它只在构建 SQL 时生效。
用 GORM 的 NowFunc 全局配置统一行为
如果你希望所有 CreatedAt、UpdatedAt 字段都自动使用数据库时间(而非本地时间),应在初始化 *gorm.DB 时配置:
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NowFunc: func() time.Time {
var t time.Time
db.Raw("SELECT NOW()").Scan(&t)
return t
},
})
这样后续调用 db.Create(&u) 时,CreatedAt 字段会真正从 MySQL 拿时间戳,而不是用 time.Now()。但要注意:这个函数会在每次创建/更新时触发一次额外查询,高并发下有开销。
比对场景中容易忽略的时区陷阱
MySQL 默认用系统时区,而 Go 的 time.Time 默认是 Local,但 GORM 在解析 db.Raw() 结果时可能不自动转换时区。常见坑点:
- 连接字符串里没加
&loc=Local或&loc=Asia%2FShanghai,导致db.Raw("NOW()")返回的时间被解析成 UTC - 数据库字段定义为
DATETIME(无时区)却期望它和 Go 的time.Time自动对齐——它不会 - 跨服务部署时,DB 和 App 容器未统一使用
tzdata或timezone配置
验证方式:执行 db.Raw("SELECT @@time_zone, NOW(), SYSDATE()").Rows(),看返回的时区是否一致、NOW 和 SYSDATE 是否同值(SYSDATE 是执行时刻,NOW 是语句开始时刻)。
最稳的做法是:所有时间比对逻辑尽量下沉到数据库侧,少做客户端计算;一旦涉及跨服务或定时任务,必须显式校验并同步时钟源,不能依赖“看起来差不多”。











