DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

PostgreSQL 同一事务中多次调用 now(),时间一直不变

PostgreSQL now 和 CURRENT_TIMESTAMP 在同一事务内固定为事务开始时间。按实际时刻或命令开始时间选择 clock_timestamp、statement_timestamp,保留事务边界并验证接收端精度。

事务已经执行了一段时间,再次查询 now(),结果仍与前一次相同。把它改成 CURRENT_TIMESTAMP 也没有变化;用这些值记录事务中不同步骤的发生时间,便可能得到一批完全相同的时间戳。

在 PostgreSQL 中,now() 代表当前事务的开始时间。需要记录函数调用时的实际时间,应使用 clock_timestamp();需要给同一个客户端命令中的操作统一标记开始时间,则考虑 statement_timestamp()。以下按 PostgreSQL 18 的时间函数语义说明,不通过拆分事务来更新时间。

在同一个连接里区分三种时间

先确认应用是否仍在同一笔事务中。即使代码没有显式写 BEGIN,客户端库也可能自动开启事务。另开一个数据库窗口查询,不能代表应用原连接的事务状态。

可在隔离测试库的新连接中做下面的只读比较。该连接应没有待提交业务,且由你直接管理事务,不放进 ORM 已管理的事务里。

先单独发送:

BEGIN;

然后发送下面的查询,稍等片刻,再把同一查询作为另一条客户端请求发送一次:

SELECT now() AS transaction_started_at,
       statement_timestamp() AS command_received_at,
       clock_timestamp() AS observed_at;

第二次查询中,transaction_started_at 应保持不变;command_received_at 对应新的命令接收时间,observed_at 对应调用时的时间。不要把两次查询打包为一个客户端命令消息来验证 statement_timestamp(),它的边界是服务器收到的最新命令消息。

比较结束后,只在这个专供比较的新连接中结束测试事务:

ROLLBACK;

这里没有写入表数据。不要把这条回滚粘到含有未提交修改的业务连接中。若应用直接查询的原始值会变化,界面却不变,还要检查显示精度或应用保留的旧值,不能只凭界面时间相同就认定是事务语义。

只替换需要记录实际时刻的表达式

now()、transaction_timestamp() 和 CURRENT_TIMESTAMP 都采用事务开始时间。三者互换不会改变结果的时间边界。

如果某个检查点需要的是“执行到这里时几点”,读取:

SELECT clock_timestamp() AS observed_at;

它返回 timestamp with time zone,即 timestamptz。旧函数 timeofday() 也读取实际时间,但结果是格式化文本;下游需要时间类型时,不必绕到文本再解析。

如果需求是同一条命令使用同一个开始时间,可保留 statement_timestamp()。它与 clock_timestamp() 的差别在较长命令里也存在:前者不会随命令执行持续前进,后者可以在同一条 SQL 内返回不同时间。

业务若刻意要求同一事务内的多条记录具有统一时间,保留 now() 就符合这个约定。不要把整个项目中的时间函数一律替换。应先区分事务批次时间、命令开始时间和实际观察时刻,再调整相应的查询、默认表达式或应用写入逻辑;涉及默认值的变更也要单独审查受影响的后续写入。

验收时间含义,不要求每次都不同

修复后,在同一测试事务内分隔一段时间读取实际观察值,确认它不再一直停留在事务起点,同时核对接收端仍按时间类型处理结果。没有必要为了这个验证修改业务表、批量重写历史记录或重启数据库。

clock_timestamp() 读取实际时间,不是生成唯一编号的函数。调用距离很近或接收端丢弃了小数位时,结果仍可能看起来相同,不能以“每次都必须不同”作为验收标准。需要唯一标识的逻辑应使用独立的标识机制。

若变更后破坏了原有的事务统一时间约定,恢复保留的表达式或应用版本。恢复代码不会自动修正已经写入的时间值;历史记录应按原始业务证据另行处理。也不要为了让 now() 前进就提前 COMMIT,那会把本应整体提交的操作拆开。