PostgreSQL 中now()是常用的时间函数,但当它在事务内部被调用时,行为可能出乎意料。开发者 Marcin 近日在开发 Emmett 库时踩到了这个坑,并在博客中详细记录了问题根因和解决方案。

核心问题在于:now()返回的是事务开始时的时间戳,而非每次调用时的当前时间。这意味着在同一事务内,无论代码运行了多久、经过了多少次重试,now() 的值始终是事务开启时的那个时间点。
这个特性为什么会引发 Bug?Marcin 遇到的具体场景是分布式锁的获取重试逻辑。当持有锁的事务超时释放后,需要用 WHERE lock_expires_at < now() 来找到已过期的锁并尝试重新获取。但如果整个重试流程都发生在同一个事务内,now()永远等于事务开始的时间——即那个锁还没有过期的时刻。WHERE 条件因此无法正确筛选出过期的锁,重试逻辑失效。
解决方案是用 clock_timestamp()替代 now()。与now()不同,clock_timestamp()每次调用都返回真实的当前时间,因此即使在事务内部也能反映时间的流逝。
两者的语义区别值得记住:now()适合在同一事务内需要所有记录共享同一时间戳的场景——例如审计日志中需要记录事务开始时间;而clock_timestamp()适合需要判断时间是否已经推进的场景——例如检查超时、处理定时任务。
这是一个典型的"看起来正确但实际埋坑"的案例。直觉上now()代表"当前时间",但 PostgreSQL 的事务语义让它在事务内部变成了一个常量。在做时间相关条件判断时,特别是涉及重试逻辑的代码,养成先问自己"这个判断是否需要真实时间"的习惯,可以避免很多难以调试的生产问题。
参考来源:Event-Driven.io(https://event-driven.io/en/how-soon-is-now-in-postgresql/)