Skip to content
返回博客
教程

PostgreSQL timestamp 列里到底存了什么?

用通俗语言讲解 PostgreSQL 的 timestamp 与 timestamptz 的存储机制、时区陷阱,以及如何为您的场景选择正确的类型。

6 分钟

PostgreSQL timestamp vs timestamptz:底层到底存了什么?

PostgreSQL 中的 timestamptimestamptz 都以同一种方式存储:一个 64 位整数,表示距离 1970-01-01 00:00:00 UTC 的微秒数。区别仅在于数据格式化给人看时的行为。

为什么容易踩坑?

  • 两个列、一个日期……却查出两个不同的结果
  • 你的应用插入了 2025-07-29 10:00,但另一个团队看到的是 02:00
  • 前端渲染的 ISO 字符串跟后端日志对不上

两罐桃子:一罐无标签,一罐有标签

数据类型官方名称存储值SELECT 时的行为
timestamptimestamp without time zone原始微秒计数原样返回 — Postgres 不做任何时区猜测
timestamptztimestamp 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 年
  • timestamptimestamptz 的存储完全一样;不同的只是解释方式

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
整理含时间字段的 JSONJSON 格式化工具粘贴数据,一键美化

所有工具完全在浏览器中运行 — 数据不会离开你的电脑。

常见问题

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 的两种时间类型都是微秒计数器;标签才是唯一的区别
  • 选错类型意味着令人困惑的时间戳和错误的计算
  • 善用工具测试、转换和验证,可以节省数小时的调试时间