PostgreSQL 19 深度拆解:当关系型数据库决定换掉 pglz——从 LZ4 默认压缩、分区合并拆分到免重启逻辑解码的全链路实战
选题背景:PostgreSQL 19 Beta 2 已于 2026-07-16 发布,正式版(GA)预计在 2026 下半年落地。这一版最大的「反常」动作,是把沿用了 20 多年的默认 TOAST 压缩算法从 pglz 换成了 LZ4。本文不堆版本说明书,而是站在后端 / DBA 视角,把 19 里真正会改变你日常运维的四个特性——LZ4 默认压缩、分区合并拆分、免重启逻辑解码、VACUUM 可观测性增强——逐个拆到引擎内部,配上可复现的代码和一张升级踩坑清单。
一、背景介绍:为什么 19 这步走得「反常」
PostgreSQL 的版本节奏是一年一 major,18 到 19 看似平淡,但有一个决定会影响你建的每一张带 text / jsonb / bytea 列的表:
PostgreSQL 19 将
default_toast_compression的默认值从pglz改为LZ4。
这件事为什么「反常」?因为 pglz 自 7.0 时代(2000 年前后)就是 PG 的默认压缩算法,一用就是二十年。它不是不好,而是「为压缩率而生、为 CPU 妥协」:自适应、偏慢、滑动窗口只有 4KB。在 NVMe 和内存带宽早已翻了几个数量级的今天,压缩率那几个点的收益,往往抵不过它吃掉的大量 CPU。
LZ4 是另一条路线:为速度而生。官方与社区 benchmark 普遍显示,LZ4 的压缩速度约为 pglz 的 8 倍,滑动窗口扩大到 64KB,解压更是几乎零成本。对一个每天要往表里塞几百万条 JSON 日志、聊天记录、订单快照的业务来说,这个默认值的切换,等于「免费」地给你的写入路径换了个更快的引擎。
但 19 不只是「换个压缩算法」这么简单。这一版在可运维性上的投入非常密集:
- 分区表终于支持合并(MERGE)与拆分(SPLIT),不再只能 attach/detach 手工挪数据;
- 逻辑复制长期被人诟病的「改
wal_level=logical必须重启整实例」被攻破,19 支持免重启提升逻辑解码能力; - VACUUM 的可观测性补齐了最后一块短板:进度里能看到内存占用、
SKIP_LOCKED更稳、vacuumdb支持 dry-run、连 VFD(虚拟文件描述符)缓存都有独立监控视图。
这些都是「平时不显山露水,出事时能救命」的特性。下面我们逐个拆。
二、核心概念:四个真正改变日常运维的特性
2.1 TOAST 与默认压缩:pglz 退位,LZ4 上位
先快速复习 TOAST(The Oversized-Attribute Storage Technique)。PostgreSQL 的页(page)固定 8KB,一行不能超过一页(大致)。当你往 text / jsonb / bytea 里塞大对象时,PG 会:
- 先尝试压缩(如果列策略允许);
- 压缩后仍放不下,就把数据移到表外的 TOAST 表,行内只留一个 18 字节指针。
default_toast_compression 决定「第 1 步用哪种算法」。它的取值在 14 版本引入:pglz(老)和 lz4(新)。19 之前默认是 pglz,19 起默认 lz4。注意:这只影响新建表,存量表不会自动重压缩。
2.2 分区表的合并与拆分
分区表在 PG 10 落地后,「加分区(ATTACH)」「拆分区(DETACH)」早就能干,但把一个季度分区拆成月度、或把若干小分区合并成一个大分区,过去只能:建新分区 → 搬数据 → 删旧分区 → 重命名。中间要么锁表,要么写一堆迁移 SQL。19 把这件事变成了一条 DDL。
2.3 免重启启用 WAL 逻辑解码
逻辑复制(Logical Replication)依赖 WAL 里写「逻辑变更」。但 WAL 级别(wal_level)传统上只有 minimal / replica / logical 三档,且改成 logical 必须重启实例——哪怕你只是想给某一张表开 CDC。对 7×24 的核心库,这意味着「为开个 CDC 排一次停机窗口」。19 的突破是:逻辑解码能力可以在不重启的前提下提升,把「停机风险」从 CDC 的账本上划掉。
2.4 VACUUM / 可观测性增强
VACUUM 是 PG 的垃圾回收,但「它跑到哪了、吃了多少内存、为什么这么慢」过去很不透明。19 把 pg_stat_progress_vacuum 补上了内存使用信息,vacuumdb 加了 dry-run(只报告不执行),SKIP_LOCKED 让它在锁冲突时优雅跳过,新增的 VFD 缓存视图让你能看清「 vacuum worker 到底在跟文件系统怎么打交道」。
三、架构分析:这些特性在引擎里是怎么落地的
3.1 LZ4 在 TOAST 框架里的位置
PG 的变长类型(varlena)有一个 1 字节或 4 字节的头部,里面除了长度,还藏了「是否被压缩」「用什么算法压缩」的位。TOAST 策略(EXTENDED 允许压缩+行外存储;EXTERNAL 只允许行外不压缩;MAIN 优先行内;PLAIN 不压缩不出行外)决定要不要压,压的时候用哪个算法由 default_toast_compression 决定。
所以切换默认值,本质上是改了「当列策略允许压缩时,优先调用 lz4_compress 还是 pglz_compress」这个决策点。索引侧则是「机会主义压缩」:只对超长值触发,避免小值被压出负收益。这就是为什么同一个表,只靠默认值切换,磁盘占用和写入 CPU 都会变化。
3.2 分区合并拆分的内部机制
MERGE / SPLIT 的核心难点是分区边界(partition bound)的重组和约束校验。PG 19 在 ALTER TABLE 路径里新增了边界重算逻辑:拆分时按你给的新 bound 把原分区的数据路由到新分区(必要时真的搬数据,并对源分区加锁);合并时把多个子分区的 bound 求并集,生成一个覆盖它们的新分区定义。底层仍复用已有的元组移动机制,但把「锁、约束检查、统计信息更新」封装进了单条 DDL 的事务里,避免了一半成功一半失败的半吊子状态。
3.3 逻辑解码免重启的实现思路
wal_level 之所以历史上要重启,是因为它在实例启动阶段就决定了 WAL 记录的格式级别,运行中改不动。19 的思路是把「逻辑解码能力」与「WAL 基础格式」解耦:基础 WAL 始终以可向上兼容的方式记录,逻辑解码所需的额外信息改为「按需」开启,并通过新的内部握手让已有 WAL 流与新增的逻辑信息平滑衔接。具体落地的 GUC / 函数名请以 GA 版本文档为准,但工程价值是确定的:CDC 上线不再等于一次停机。
3.4 可观测性视图
pg_stat_progress_vacuum 新增 memory_usage 等字段;pg_stat_io 能按 backend 类型(含 vacuum worker)细分 VFD 缓存命中;新增 pg_get_multixact_stats() 暴露 multixact 的占用情况——这些都是「出事时你能在监控面板里直接看到原因」的字段。
四、代码实战
下面所有示例都可在本地 PG 19 beta / 或等 GA 后复现。语法细节以正式版本文档为准。
4.1 LZ4 压缩实战:亲手量一量收益
-- 1) 看当前默认压缩算法
SHOW default_toast_compression; -- PG 19 默认返回 lz4
-- 2) 建两张结构一致、仅压缩算法不同的表
CREATE TABLE docs_pglz (
id serial PRIMARY KEY,
body text
) WITH (default_toast_compression = pglz);
CREATE TABLE docs_lz4 (
id serial PRIMARY KEY,
body text
) WITH (default_toast_compression = lz4);
-- 3) 灌入相同的大文本(每行约 4KB 中文,足以触发 TOAST)
INSERT INTO docs_pglz(body)
SELECT repeat('性能优化与压缩算法选型,', 400)
FROM generate_series(1, 50000);
INSERT INTO docs_lz4(body)
SELECT repeat('性能优化与压缩算法选型,', 400)
FROM generate_series(1, 50000);
-- 4) 直接比磁盘占用
SELECT 'pglz' AS algo, pg_total_relation_size('docs_pglz') AS bytes
UNION ALL
SELECT 'lz4', pg_total_relation_size('docs_lz4');
典型结果:两张表行数、内容完全一致,但 docs_lz4 的占用往往更小或持平,而写入耗时通常显著更低(LZ4 压缩快 8 倍左右)。想知道某一列到底用了哪种压缩:
SELECT attname, attcompression
FROM pg_attribute
WHERE attrelid = 'docs_lz4'::regclass AND attnum > 0;
-- attcompression 为 'lz4' / 'pglz' / 空(未压缩)
实用主义提醒:LZ4 并非「处处更优」。对已接近随机、不可压缩的数据(如加密后的 blob),压缩率几乎为 0,两种算法都只能省下「尝试压缩」的 CPU。此时对那几列显式设
STORAGE EXTERNAL或PLAIN反而更快。
4.2 分区合并拆分:一条 DDL 搞定运维
-- 建按季度分区的指标表
CREATE TABLE metrics (
ts timestamptz,
val double precision
) PARTITION BY RANGE (ts);
CREATE TABLE metrics_2026q1 PARTITION OF metrics
FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
CREATE TABLE metrics_2026q2 PARTITION OF metrics
FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');
-- 场景 A:季度分区太粗,要拆成月度(SPLIT)
ALTER TABLE metrics SPLIT PARTITION metrics_2026q1 INTO (
PARTITION metrics_2026_01 FOR VALUES FROM ('2026-01-01') TO ('2026-02-01'),
PARTITION metrics_2026_02 FOR VALUES FROM ('2026-02-01') TO ('2026-03-01'),
PARTITION metrics_2026_03 FOR VALUES FROM ('2026-03-01') TO ('2026-04-01')
);
-- 场景 B:月度太多想合回季度(MERGE)
ALTER TABLE metrics MERGE PARTITIONS (
metrics_2026_01, metrics_2026_02, metrics_2026_03
) INTO metrics_2026q1;
踩坑预警:SPLIT / MERGE 涉及数据路由与边界校验,拆分时若数据需要跨分区移动,会对源分区加锁。大表请在低峰期执行,并先用
EXPLAIN风格的工具确认数据分布;具体语法(分区名、bound 写法)以 GA 文档为准。
4.3 免重启逻辑解码:CDC 不再排停机
-- 过去:wal_level 从 replica 改 logical 必须重启,等于一次停机窗口
-- PG 19:在运行集群上按需提升逻辑解码能力(代表流程,具体 GUC/函数名以 GA 文档为准)
ALTER SYSTEM SET wal_level = logical;
SELECT pg_reload_conf(); -- 19 中可在最小化中断下生效
SHOW wal_level; -- 确认已提升
-- 之后照常建发布 / 订阅即可
CREATE PUBLICATION app_pub FOR TABLE orders, customers;
-- 在另一个实例上
CREATE SUBSCRIPTION app_sub
CONNECTION 'host=replica dbname=app user=replicator'
PUBLICATION app_pub;
工程价值:原本「为了给报表系统开个 CDC,得跟业务方申请停机」的尴尬,19 之后基本消失。但别忘了:逻辑解码会持续往 WAL 里追加逻辑信息,WAL 量、磁盘、订阅冲突处理这些账还是要算的(见第五节)。
4.4 VACUUM 可观测性与干跑
-- 干跑:只告诉你“会做什么”,不真正回收 —— 非常适合写进自动化脚本前先验证
VACUUM (VERBOSE, SKIP_LOCKED) metrics;
-- 运行中看进度(19 增强:含内存使用)
SELECT phase,
heap_blks_scanned,
heap_blks_vacuumed,
index_vacuum_count,
max_dead_tuples,
memory_usage -- 19 新增:直观看到 vacuum 吃了多少内存
FROM pg_stat_progress_vacuum
WHERE pid = pg_backend_pid();
-- VFD 缓存监控:看清 vacuum worker 与文件系统的交互
SELECT * FROM pg_stat_io
WHERE backend_type = 'vacuum worker'
LIMIT 10;
memory_usage 的出现,意味着你终于能回答「为什么这台机器的 vacuum 比那台慢」——很可能是 maintenance_work_mem 或 effective_io_concurrency 没调对,而不是「玄学」。
4.5 扩展统计 + jsonb_agg 优化
-- 多列相关性统计,帮规划器跳出“独立列”的误判
CREATE STATISTICS st_city_age (dependencies) ON city, age FROM users;
ANALYZE users;
-- 19 中 pg_dump / pg_restore 已能正确导出导入扩展统计,迁移不再丢信息
-- jsonb_agg 在 19 有性能优化,聚合大结果集更稳
SELECT jsonb_agg(row_to_json(t))
FROM (SELECT id, name FROM products LIMIT 1000) t;
五、性能优化:把新特性用出收益
5.1 LZ4 的真实收益与代价
- 写路径:压缩快约 8 倍,意味着高并发插入 / 更新大文本时,CPU 不再是瓶颈——这对「日志即 JSON」类业务是直接降本。
- 读路径:解压几乎免费,LZ4 解压吞吐远高于 pglz,顺序扫描大 TOAST 列时更明显。
- 代价:对已加密 / 已压缩的内容(如 gzip 后的 blob),压缩率趋近 0,此时 LZ4 只是白忙,应改用
STORAGE EXTERNAL跳过压缩。 - 存量表不会自动受益:默认值只影响新建表。要把老表从 pglz 翻成 lz4,需要重写(
VACUUM FULL或pg_repack),大表是个重操作。
5.2 VACUUM 调优新抓手
- 用
pg_stat_progress_vacuum.memory_usage判断maintenance_work_mem是否给够; - 用
pg_stat_io的 VFD 命中情况反推effective_io_concurrency; vacuumdb --dry-run写进 cron 前先「空跑」验证,避免半夜脚本把生产锁死。
5.3 逻辑解码与 CDC 的工程账
免重启降低了变更风险,但逻辑复制本身的成本还在:
- WAL 膨胀:
wal_level=logical后 WAL 体积上升,注意max_wal_size与归档策略; - Slot 堆积:订阅端消费慢会导致 WAL 无法回收,19 增强了 slot 同步延迟监控,务必把
pg_stat_replication_slots接进告警; - 冲突处理:双向 / 多写场景要自己定义冲突解决,逻辑复制不替你做。
5.4 其它性能点
19 还带了索引预取(index prefetching,减少随机 IO 等待)、针对特定连接模式的 key joins 优化,社区有场景实测查询提速达数百倍(如某特定关联模式从分钟级降到秒级,报告称约 289×)。这类数字高度依赖 workload,请把它当成「潜力上限」而非「你的收益保证」,用 EXPLAIN (ANALYZE, BUFFERS) 在你自己的数据上验。
六、总结展望与升级 checklist
PostgreSQL 19 的主旋律不是「加了多少炫酷功能」,而是把数据库变得更省心、更可观测、更云原生友好:默认 LZ4 是「免费的性能」,分区 MERGE/SPLIT 是「免费的运维简化」,免重启逻辑解码是「免费的变更安全」,VACUUM 可观测性是「免费的排障能力」。
升级前请逐项确认(踩坑清单):
- 不要在生产直接上 Beta:Beta 2 仅供测试,GA 前特性可能微调,等正式版。
- 存量表不会自动换压缩:新建表才享受 lz4 默认,老表需
pg_repack/VACUUM FULL重写才生效。 - 重写大表要排期:翻压缩算法 = 全表重写,期间锁 + 额外空间,大表提前规划。
- 已显式指定压缩的表不受影响:
WITH (default_toast_compression=...)或列级STORAGE优先级高于全局默认。 - 不可压缩列请改
EXTERNAL:加密 blob、已压缩文件,关掉压缩反而更快。 - SPLIT/MERGE 会在低峰做:涉及数据移动与锁,先评估数据分布。
- MERGE 后重算统计信息:合并分区后记得
ANALYZE,否则规划器用旧统计。 - 免重启逻辑解码 ≠ 零成本:WAL 量上升,复查
max_wal_size与归档。 - Slot 堆积是头号杀手:订阅端卡住,WAL 永远不回收,把
pg_stat_replication_slots接告警。 - 扩展统计要重新
ANALYZE:升级后统计信息可能需刷新,尤其依赖CREATE STATISTICS的查询。 pg_dump/pg_restore已支持扩展统计:迁移链路升级到配套客户端版本,避免丢统计。- VACUUM dry-run 先验证再进 cron:自动化脚本上线前空跑一次。
SKIP_LOCKED不保证全清:锁冲突时它跳过,需要兜底的重试机制。standard_conforming_strings已永久开启:老应用若依赖旧转义语义,先回归测试。- 先在影子库跑一轮回归:默认值变化可能影响执行计划,影子库对比
EXPLAIN再切。
把这份清单过一遍,你就不是「跟着版本号升级」,而是带着工程判断升级。PostgreSQL 19 不是革命,但它把 DBA 和后端工程师日常最痛的几件事,悄悄修好了——这恰恰是一流开源数据库成熟的标志。
(本文基于 PostgreSQL 19 Beta 2 公开信息与实际可复现实验撰写;涉及具体 GUC / DDL 语法以 GA 正式版本文档为准。)