KernelRanch
返回文章列表

timestamp 与 timestamptz:三个函数、两个时区、一个常见错误

2026-11-10 #数据库

时间类型出错通常不是语法问题,是语义问题:存进去的到底是"某个时刻"还是"墙上时间"。这两者在跨时区、跨夏令时的时候会分叉。

1. 两种类型

类型存储含义字节
timestamp8 字节不带时区的"墙上时间",存什么读什么8
timestamptz8 字节存 UTC 时刻,按会话 TimeZone 渲染8

两者都是 8 字节(本机 pg_type.typlen 实测),所以选型不是空间问题,是语义问题。

规则很简单:记录"什么时候发生"用 timestamptz;记录"当地几点"(比如营业时间、闹钟)用 timestamp。混着用就会出错。

2. 三个"现在"

SELECT now(), statement_timestamp(), clock_timestamp();

本机实测输出:

2026-11-10 05:52:57.8938+00 | 2026-11-10 05:52:57.8938+00 | 2026-11-10 05:52:57.894057+00

now() 与 statement_timestamp() 在这一行里相同,因为都在语句开始时取值;clock_timestamp() 是真正调用时刻。差别在长事务里才显现:

函数取值时机用途
now() / transaction_timestamp()事务开始同一事务内保持一致,适合记业务时间
statement_timestamp()语句开始语句级审计
clock_timestamp()函数调用瞬间计时、测量耗时

用 now() 给循环里的多行打时间戳,会得到完全相同的时间——这通常是想要的;想测每步耗时,就得用 clock_timestamp()。

3. 三个时区坑

列类型写成 timestamp 却存 now()。now() 返回 timestamptz,插进 timestamp 列会按会话时区转成本地墙上时间,丢失时区信息。之后换一个时区的会话去读,得到的是"错的时刻"。这类表的典型症状是:报表在东八区正确,UTC 环境下差 8 小时。

跨夏令时。timestamptz 存的是绝对时刻,没有问题;但用 timestamp 做"每天 9 点"这类计算时,夏令时切换当天可能多一小时或少一小时。

AT TIME ZONE 的方向容易记反。timestamptz AT TIME ZONE 'Asia/Shanghai' 得到的是该时区的墙上时间(timestamp);反过来 timestamp AT TIME ZONE 'Asia/Shanghai' 得到的是对应的绝对时刻(timestamptz)。记法:AT TIME ZONE 总是"理解为"后面那个时区。

4. 建表与查询建议

-- 推荐:存绝对时刻
CREATE TABLE events (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  created_at timestamptz NOT NULL DEFAULT now()
);

-- 按时区做日聚合:先转墙上时间再截断
SELECT date_trunc('day', created_at AT TIME ZONE 'Asia/Shanghai') AS d, count(*)
FROM events GROUP BY d ORDER BY d;

小结

timestamptz 是默认选择:它存绝对时刻,展示交给会话时区。timestamp 只在"这个时间与地点绑定"的场景下用,比如门店营业时间。

三个函数的区别记住一句话就够:now() 跟事务、statement_timestamp() 跟语句、clock_timestamp() 跟调用。混用它们不会报错,只会让时间戳看起来对、算起来错。

数据来源与检索时间

检索时间 2026-11-10。字节数与函数输出为本机 18.6 实测。

来源URL对应数据
PostgreSQL 18 文档《8.5 日期/时间类型》https://www.postgresql.org/docs/18/datatype-datetime.htmltimestamp 与 timestamptz 的存储语义、8 字节宽度、时区处理规则
PostgreSQL 18 文档《9.9 日期/时间函数》https://www.postgresql.org/docs/18/functions-datetime.htmlnow()/transaction_timestamp()/statement_timestamp()/clock_timestamp() 的取值时机、AT TIME ZONE 的方向语义

核验说明

  • 8 字节宽度来自 pg_type.typlen 实查(timestamp 与 timestamptz 均为 8),三个函数的时间戳来自同一次 SELECT 的真实输出。
  • 未做:没有构造夏令时时区的实际数据来验证第三条坑,该条依据文档语义;没有测 timestamptz 与 timestamp 在索引上的差异(两者都是 8 字节定长,实际差异很小)。