PostgreSQL timestamp vs timestamptz:底层到底存了什么?
PostgreSQL 中的 timestamp 和 timestamptz 都以同一种方式存储:一个 64 位整数,表示距离 1970-01-01 00:00:00 UTC 的微秒数。区别仅在于数据格式化给人看时的行为。
为什么容易踩坑?
- 两个列、一个日期……却查出两个不同的结果
- 你的应用插入了
2025-07-29 10:00,但另一个团队看到的是02:00 - 前端渲染的 ISO 字符串跟后端日志对不上
两罐桃子:一罐无标签,一罐有标签
| 数据类型 | 官方名称 | 存储值 | SELECT 时的行为 |
|---|---|---|---|
timestamp | timestamp without time zone | 原始微秒计数 | 原样返回 — Postgres 不做任何时区猜测 |
timestamptz | timestamp with time zone | 同样的微秒计数 | Postgres 在发送前应用会话的 TimeZone 设置 |
类比
timestamp= 一罐没贴产地标签的桃子。你知道是水果,但不知道在哪儿罐装的。timestamptz= 一罐印着”产地 UTC+8”的桃子。打开的人可以自行决定是否换算。
底层原理:就是一个大数字
2000-01-01 00:00:00 UTC → 0
2000-01-01 00:00:01 UTC → 1 000 000
- 单位:微秒(百万分之一秒)
- 范围:公元前 4713 年 – 公元 294276 年
timestamp和timestamptz的存储完全一样;不同的只是解释方式
15 秒速览 Demo
-- 客户端使用上海时区
SET TimeZone = 'Asia/Shanghai';
CREATE TABLE demo (
created_ts timestamp,
created_tz timestamptz
);
INSERT INTO demo VALUES ('2025-07-29 10:00', '2025-07-29 10:00');
| 查询 | 结果 | 原因 |
|---|---|---|
SELECT created_ts FROM demo; | 2025-07-29 10:00:00 | 原始值,不做时区运算 |
SELECT created_tz FROM demo; | 2025-07-29 10:00:00+08 | 输出时贴上标签 |
SET TimeZone = 'UTC'; 后查询 | 2025-07-29 02:00:00+00 | 同一时刻,换个视角 |
常见陷阱与快速修复
1. 不同用户,不同时钟
- 原因:各客户端使用不同的
TimeZone设置来读取timestamptz - 修复:要么全部用
timestamp并约定统一时区,要么在连接初始化时强制SET TimeZone = 'UTC'
2. 用错类型存”墙上时间”
- 业务日历(营业时间、截止日期)应使用
timestamp - 跨境业务流(订单、日志)应以 UTC 存入
timestamptz
3. API 漂移
timestamptz始终以 ISO-8601 带偏移量的字符串传输(Z或+08:00)- 让前端 UI 根据用户本地时区格式化
速查表:该用哪个?
仅本地日历 → timestamp
涉及全球 → timestamptz(存 UTC)
- 财务报表、排课表 →
timestamp - 审计日志、电商订单 →
timestamptz
用 Go Tools 快速验证
| 需求 | 工具 | 用法 |
|---|---|---|
| 查看 SQL 中的 epoch 值 | 时间戳转换器 | 粘贴 1690622400,点击转换 |
| 一眼比较两个时区 | 时区转换器 | 输入 10:00 Asia/Shanghai |
| 整理含时间字段的 JSON | JSON 格式化工具 | 粘贴数据,一键美化 |
所有工具完全在浏览器中运行 — 数据不会离开你的电脑。
常见问题
PostgreSQL 中 timestamp 和 timestamptz 有什么区别?
timestamp(不带时区)按原样存储日期时间值,没有任何时区上下文。timestamptz(带时区)在存储时将输入转换为 UTC,在检索时再转换回会话时区。几乎所有场景都应使用 timestamptz —— 它能防止分布式系统中与时区相关的 bug。
PostgreSQL 真的会在 timestamptz 中存储时区吗?
不会 —— 尽管名字如此,PostgreSQL 并不存储时区本身。它将输入转换为 UTC,仅存储 UTC 值(从 2000-01-01 起的微秒计数)。检索时,它根据会话的 timezone 设置从 UTC 转换为对应时区。原始时区信息会被丢弃。
如何更改 PostgreSQL 会话的时区?
执行 SET timezone = 'America/New_York'; 即可更改会话时区。这会影响 timestamptz 值的显示和解释方式。如需修改服务器级默认值,在 postgresql.conf 中设置 timezone。建议始终使用 IANA 时区名称(如 Asia/Shanghai)而非缩写(如 CST),以避免歧义。
存储事件时间应该用 timestamp 还是 timestamptz?
几乎所有场景都应使用 timestamptz —— 用户操作、API 调用、审计日志和定时任务。仅在存储与特定时刻无关的抽象时间时才使用 timestamp(不带时区),例如”商店 09:00 开门”表示当地时区的上午 9 点,而非某个具体的 UTC 时刻。
PostgreSQL 的 timestamptz 如何处理夏令时?
PostgreSQL 使用 timestamptz 时能正确处理夏令时,因为它内部以 UTC 存储所有数据。检索值时,PostgreSQL 根据会话时区的当前夏令时规则从 UTC 进行转换。这意味着同一个 UTC 时刻在夏令时切换前后会正确显示不同的本地时间。
总结
- Postgres 的两种时间类型都是微秒计数器;标签才是唯一的区别
- 选错类型意味着令人困惑的时间戳和错误的计算
- 善用工具测试、转换和验证,可以节省数小时的调试时间