NOW 与 clock_timestamp 的差别在冻结时机
PostgreSQL 提供多种“当前时间”。事务时间标记事务开始,语句时间标记客户端命令到达,墙钟读数则在表达式求值时读取当前时钟。选择不同函数,会改变审计字段、过期检查和长任务中间观测的含义。应先明确要记录哪个事件,再决定函数,不能因为名称都有时间含义就随意互换。 一个事务只有一个开始时刻 now()、transaction_timestamp() 与不带精度参数的 CURRENT_TIMESTAMP,返回同一个事务开始瞬间。在显式事务里,它们始终固定,即使后续语句已经晚了几秒或几分钟。CURRENT_TIMESTAMP 可以指定精度来舍入结果;小数位数的处理与采样时刻是两回事,不能因为显示精度不同就推断时钟来源改变。 这种稳定性适合让相关行共享同一个逻辑事务时间。它不记录每行真正插入的时刻,也不是最终提交时间。事务可能先等待锁或执行大量计算,随后才插入数据,最后还有可能回滚。把列命名为 created_at,并不能自动说明默认值到底代表哪个事件,需要在数据契约里写清楚。 普通自动提交模式下,连续语句通常各自处于不同事务,因此 now 看起来会随每条语句前进。但这种观察没有改变函数契约。后来为了原子性加上显式事务时,原本被误认为实时变化的时间,就可能暴露出早已固定的问题;验证时应覆盖真实事务边界,而不只在交互终端连续敲两条查询。 语句标记与实时读数 statement_timestamp() 返回服务器收到最近一条客户端命令消息的时间,在该命令执行期间保持稳定。事务中后续命令的语句时间可能与事务时间不同。同一协议消息里发送的多条 SQL 命令,也可能共享这个标记,所以它不一定代表等待结束后,每个内部操作真正开始的那一刻。 clock_timestamp() 在求值时读取当前墙钟,即使同一条语句内部也可能变化。由于时钟精度有限,两次调用并不保证得到不同值。它也不是单调计时器,时钟校正可能影响前后读数的差值。需要保证耗时测量不受墙钟调整影响时,应使用合适的单调时钟,而不是把数据库墙钟当成无条件递增的计数器。 这些值单独使用,都不能证明并发事务的提交顺序,也不能提供唯一事件身份。相关字段需要一致逻辑时间时选事务标记,观测命令到达时选语句标记,需要读取当前墙钟时才选实时观测。身份与顺序要求应另外明确,不能让时间戳自动承担它未承诺的语义。 坏掉的样子 长事务中的过期检查如果使用 now,就会持续与事务开始时刻比较。一个在事务开始时成立的条件,即使真实墙钟已经越过截止时间,也可能继续成立。反过来,把全部默认值换成 clock_timestamp,又可能让本该共享逻辑创建时间的相关行各不相同。修改函数前,应先确定列代表的是业务批次、命令到达还是实际观测。 列默认值中的特殊字面量 TIMESTAMPTZ 'now' 也是一个陷阱。PostgreSQL 在解析常量时就把它转换成时间,所以后续插入可能一直拿到建表时刻。DEFAULT now() 是函数调用,会在真正使用默认值时求值。两种写法看起来相似,并不代表求值生命周期相同;只检查默认表达式包含 now 这个词,会漏掉这个差别。 这些函数返回 timestamptz。切换会话时区会改变瞬间的显示形式,而不是改变已存储的瞬间本身。timestamptz 不保留最初传入的时区名称;应用需要记住原始时区时,应单独建模,不能从后来显示的偏移量反推原始地区。相同偏移量也不足以唯一确定某个时区名称。 快速验证 把下面内容保存为 timestamps.sql,在可丢弃的 PostgreSQL 18 数据库中执行 psql -X -v ON_ERROR_STOP=1 -f timestamps.sql。显式事务之外,应保持 psql 默认的自动提交行为;第二段需要让两次插入分别处在不同事务里。所有表都是临时表。 ...