事情的起因是一次凌晨三点的告警。
我们的主数据库(PostgreSQL 16)突然 CPU 打满,磁盘 IO 飞涨。排查了一圈,发现罪魁祸首是一个看起来完全无辜的表——user_sessions。这个表承担着会话存储的功能,数据量 2000 万左右,表面上看毫无问题。
但仔细一看,它的写入 QPS 是 3000/s。
每个用户操作都要写一条会话记录,而 PostgreSQL 默认会把这 3000 次写入变成 3000 次磁盘 IO——因为每个操作都要写 WAL(Write-Ahead Log)。这不是 bug,这是 PostgreSQL 的核心设计:日志先行,保证崩溃可恢复。
但问题是:这个表丢了就丢了,没什么大不了的。用户重新登录一下就好。我为什么要为它付三倍磁盘 IO 的代价?
UNLOGGED 表:不写日志的代价与收益
PostgreSQL 有一个很少被讨论的表类型:UNLOGGED TABLE。它会完全绕过 WAL 机制。
CREATE UNLOGGED TABLE user_sessions (
session_id text PRIMARY KEY,
user_id bigint NOT NULL,
created_at timestamptz DEFAULT now(),
data jsonb
);
改完之后,写入 QPS 从 3000 变成 9000,CPU 降了一半,磁盘 IO 几乎为零。
但代价是什么?
崩溃之后数据全丢。 PostgreSQL 重启会直接清空所有 UNLOGGED 表。这在某些场景下是不可接受的——比如订单表、支付记录、财务数据。但对于会话缓存、临时计算结果、ETL 中间层?这简直是完美的设计。
常见的误解:UNLOGGED = 不重要
很多人看到"不写日志",第一反应是"那这东西肯定不可靠,不能用"。这个等式太粗暴了。
让我来拆解一下这个逻辑:
- 日志的目的是什么? 保证 durability(持久性)——崩溃后能恢复到一致状态。
- 什么东西需要 durability? 业务关键数据,丢了会影响业务正确性的数据。
- 什么东西不需要 durability? 可以从其他数据源重建的数据,或者丢了也没什么大不了的数据。
会话数据明显属于第三类。用户退出重登就好,何必用昂贵的 WAL 来保护它?
但更危险的一个误解是:有人把 UNLOGGED 和"临时表"混淆。UNLOGGED 不是临时表——数据在 session 断开时不会自动清除,它只是不写日志而已。
实战场景:什么时候该用,什么时候绝对不该
✅ 可以用 UNLOGGED 的场景
1. 会话/状态缓存
用户的登录状态、购物车内容、临时草稿——这些东西丢了用户能接受,用 UNLOGGED 性能收益巨大。
2. ETL 和数据 pipeline 的中间表
如果你在做数据同步,中间有一个 staging 表,数据是从上游拉来的,丢了可以从上游重刷。这种场景下写 WAL 完全是浪费。
3. 批量导入的暂存区
大批量导入数据时,先导入 UNLOGGED 表,再通过 INSERT INTO ... SELECT 写入主表,可以快 5-10 倍。因为导入过程本身不需要 WAL 的保护。
4. 机器学习特征存储
实时特征计算的结果,用完即弃,或者有上游特征服务兜底,用 UNLOGGED 毫无心理负担。
❌ 绝对不要用 UNLOGGED 的场景
1. 任何有外键依赖的数据
UNLOGGED 表的外键约束不会强制执行(严格来说是部分不保证),而且级联删除也不生效。
2. 需要复制的数据库
流复制和逻辑复制都会跳过 UNLOGGED 表。如果你的数据需要同步到从库,别碰 UNLOGGED。
3. 核心业务表
订单、支付、用户账户——这些东西你用 UNLOGGED,迟早有一天会体会到什么叫"中年危机"。
4. 任何有唯一性约束且约束被破坏会有问题的场景
UNLOGGED 表的唯一性约束在特定情况下可能出问题(虽然官方说不会,但工程实践告诉我们:别赌)。
一个反直觉的事实:UNLOGGED 还能加速索引
UNLOGGED 不仅跳过 WAL 写入,还会在创建索引时跳过 WAL 记录。这意味着:
-- 普通表创建索引需要写 WAL
CREATE INDEX idx ON orders(user_id);
-- UNLOGGED 表创建索引快得多
CREATE UNLOGGED TABLE order_staging (...);
CREATE INDEX idx ON order_staging(user_id);
在大量数据导入场景中,先建 UNLOGGED 表,批量导入数据,创建索引(无 WAL 开销),最后再通过 INSERT INTO ... ON CONFLICT 合并到主表——这是 PostgreSQL 里最快的批量导入模式。
我在生产环境实测:1 亿条数据从 4 小时压缩到 40 分钟。
工程哲学:不要为不需要的可靠性付钱
回到凌晨三点那次告警。
问题的本质不是 UNLOGGED 表有多好,而是我们为可靠性付出了不必要的成本。PostgreSQL 的 WAL 是一个伟大的设计,它让数据库崩溃后可恢复。但它的成本是真实的——磁盘 IO、存储空间、写放大。
很多工程师(包括我自己以前)有一种隐形的"可靠性洁癖":所有数据都要可靠存储,所有表都要有 WAL,所有索引都要有唯一性约束。这不是工程思维,这是恐惧驱动的设计。
工程思维是:我需要为这段数据付出多少可靠性成本?这个成本相对于数据丢失的风险是否值得?
会话数据丢失的风险:用户重新登录。订单数据丢失的风险:公司倒闭。哪个值得 WAL?一目了然。
结论
UNLOGGED TABLE 不是 PostgreSQL 的"危险功能",它是给认真理解系统的工程师准备的高级工具。
用它的时候,你必须清楚自己在做什么:数据丢了怎么办?需不需要复制?有没有外键依赖?
如果这三个问题你都有明确答案,那么 UNLOGGED 可以让你的系统快一倍、便宜三倍。
如果你不知道答案,那就老老实实用普通表——别到时候数据丢了来骂 PostgreSQL 不靠谱。
工具没有好坏,只有用对用错。