Skip to content
返回博客
教程

PostgreSQL timestamp 列里到底存了什么?

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

Go Tools Team 6 分钟

PostgreSQL timestamp 列里到底存了什么?

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 格式化工具粘贴数据,一键美化

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

总结

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