PostgreSQL 中 now() 函数事务内行为异常,clock_timestamp() 成解决方案
【导语:开发者 Marcin 在开发 Emmett 库时,发现 PostgreSQL 中 now() 函数在事务内行为异常,导致分布式锁获取重试逻辑失效,最终找到用 clock_timestamp() 替代的解决方案。】
PostgreSQL 中 now() 是常用的时间函数,但在事务内部调用时,行为出乎意料。它返回的是事务开始时的时间戳,而非每次调用时的当前时间。这意味着在同一事务内,无论代码运行多久、经过多少次重试,now() 的值始终是事务开启时的时间点。
Marcin 遇到的具体场景是分布式锁的获取重试逻辑。当持有锁的事务超时释放后,需要用 WHERE lock_expires_at < now() 来找到已过期的锁并尝试重新获取。但由于整个重试流程在同一事务内,now() 永远等于事务开始的时间,WHERE 条件无法正确筛选出过期的锁,重试逻辑失效。
解决方案是用 clock_timestamp() 替代 now()。与 now() 不同,clock_timestamp() 每次调用都返回真实的当前时间,即使在事务内部也能反映时间的流逝。
now() 适合在同一事务内需要所有记录共享同一时间戳的场景,例如审计日志中需要记录事务开始时间;而 clock_timestamp() 适合需要判断时间是否已经推进的场景,例如检查超时、处理定时任务。
编辑观点:此案例提醒开发者在使用 PostgreSQL 时间函数时,要充分考虑事务语义,避免因函数特性引发难以调试的生产问题。
