PostgreSQL 19 Beta 2 深度解析:JIT 默认关闭、并行 Autovacuum、SIMD 加速 COPY——你的生产库该怎么接招
2026 年 7 月 16 日,PostgreSQL 全球开发组发布了 PostgreSQL 19 Beta 2。距离正式版 GA(按惯例是每年 9 月底到 10 月初)还有两个多月,但核心特性已经基本冻结。这个版本的变化比 PG 18 更"激进":JIT 默认关闭、standard_conforming_strings 强制开启、RADIUS 认证直接移除、TOAST 默认压缩换成 lz4——每一条都可能砸到生产环境。本文基于官方 Release Notes 和 Beta 2 实测,把值得你关注的变化逐条拆开讲透。
一、背景:为什么 PG 19 是一个"清理债务"的版本
如果说 PostgreSQL 18 的主线是 I/O 子系统重构(AIO)和 uuidv7 这类"加法",那 PG 19 的主线就是"减法"和"纠偏"。
翻一遍 Release Notes 的 Migration 章节你会发现,这个版本的不兼容变更数量明显多于往年。社区在这个版本里干了几件憋了很多年的事:
- 把 JIT 的默认值改成了 off。JIT 从 PG 11 引入至今 8 个大版本,靠优化器代价估算来决定是否启用,但这套代价模型被证明"不可靠"(unreliable,官方原话)。大量 OLTP 用户被 JIT 编译开销坑过——一条本来 2ms 的查询,因为触发了 JIT 编译变成 200ms。
- 强制
standard_conforming_strings = on。这个变量从 PG 9.1 开始默认开启,但一直允许关闭。15 年过去了,还在依赖\'转义的旧代码理论上早该死透了,PG 19 直接在服务端焊死。 - 移除 RADIUS 认证。因为 PostgreSQL 只支持 UDP 上的 RADIUS,而这个传输方式"unfixably insecure"(无法修复的不安全)——社区的态度是:修不好的东西,就删掉。
- MD5 密码认证成功后也会告警。MD5 在 PG 18 已标记废弃,PG 19 里即使认证成功也会打 warning,通过
md5_password_warnings可以关掉,但这明显是移除前的最后通牒。
这种"清理债务"的姿态,对老系统维护者不算友好,但对整个生态是好事。下面进入正题,按影响面从大到小逐个分析。
二、破坏性变更:升级前必须过一遍的清单
2.1 JIT 默认关闭——分析型负载用户注意
-- PG 18 及之前:JIT 默认开启,由代价模型决定是否触发
SHOW jit; -- on
SHOW jit_above_cost; -- 100000
-- PG 19:默认关闭
SHOW jit; -- off
这个变化的逻辑链条值得说清楚。JIT 的触发条件是查询预估总代价超过 jit_above_cost(默认 100000)。问题在于:
- 代价估算本身就不准。统计信息略有偏差,一个 8 万代价的查询可能被估成 12 万,白白吃一次 JIT 编译(几十到几百毫秒)。
- 编译开销没有被计入代价模型。优化器决定"要 JIT"的时候,并不知道编译本身要花多久。
- 收益场景太窄。JIT 只对表达式计算密集的大型分析查询有明显收益,OLTP 场景几乎纯亏。
实际影响:如果你在 PG 上跑数仓/报表类负载(大量聚合、复杂表达式、单查询秒级以上),升级到 19 之后需要手动开启:
-- 全局开启(不推荐直接全局开)
ALTER SYSTEM SET jit = on;
-- 更稳妥的做法:只给分析会话/角色开
ALTER ROLE report_user SET jit = on;
-- 或者在连接池层面按业务分组设置
-- pgbouncer: connect_query = 'SET jit = on'
我的建议是:借这次升级做一次 JIT 收益审计。用 EXPLAIN (ANALYZE, TIMING) 对比开关 JIT 的耗时,很多团队会发现自己这几年一直在为 JIT 付出编译成本却没拿到收益。
2.2 max_locks_per_transaction 默认值 64 → 128,但语义变了
这条容易被误读。PG 19 修改了锁表的内存分配方式,max_locks_per_transaction 的默认值从 64 变成 128,但实际容量不变——官方明确说"settings must now be doubled to match their capacity in previous releases"。
也就是说,如果你之前显式设置了 max_locks_per_transaction = 256,升级后要改成 512 才能维持同等容量。分区表大户(单查询涉及几百个分区锁)要特别注意,否则会撞上:
ERROR: out of shared memory
HINT: You might need to increase max_locks_per_transaction.
2.3 TOAST 默认压缩从 pglz 换成 lz4
-- PG 19
SHOW default_toast_compression; -- lz4
lz4 从 PG 14 就可用了,压缩速度比 pglz 快 3-5 倍,解压快一个数量级,压缩率略低(通常差 5%-10%)。PG 19 终于把默认值切了过来。
注意几点:
- 只影响新写入的 TOAST 数据,存量数据保持原压缩格式,读取兼容。
- 编译时需要
--with-lz4,主流发行版包都带,自编译的注意。 - 如果你的场景极端在意存储空间(比如大 JSONB 冷数据),可以按列指定回 pglz,甚至上 zstd 方案(等社区后续支持):
ALTER TABLE events ALTER COLUMN payload SET COMPRESSION pglz;
实测参考:在 1KB-16KB 的 JSONB 负载下,lz4 写入吞吐比 pglz 高 30% 以上,CPU 占用明显下降。对绝大多数业务这是纯赚。
2.4 其他会咬人的变更
- 数据库名/角色名/表空间名禁止包含回车和换行,
pg_upgrade会直接拒绝含此类名称的集群升级。这是安全修复(防注入类攻击面),但如果你有历史遗留的奇葩命名,升级前先改名。 - inet/cidr 的默认索引 opclass 从 btree_gist 换成 GiST,因为 btree_gist 的 inet/cidr opclass 存在会漏返回行的正确性 bug。含此类索引的集群
pg_upgrade会被拦下,需要先重建索引。 json_array()空结果从 NULL 变为空数组[],依赖 NULL 判断的业务逻辑要排查。- READ ONLY 事务状态会传递给 postgres_fdw 远端会话,之前"本地只读、远端偷偷写"的邪道用法失效了。
- COPY FROM ... WHERE 不再允许引用系统列(ctid 等),这类值本来就没有良定义。
三、优化器:Richard Guo 的"NULL 语义大扫除"
看 PG 19 的优化器改动列表,一个名字出现频率极高:Richard Guo。这个版本优化器方向的主题可以概括为——把 NULL 相关的表达式化简做彻底,让更多查询模式能走到高效执行路径。
3.1 NOT IN 终于能转 ANTI JOIN 了(有条件)
这是我个人最期待的一条。写过 SQL 优化的都知道一个古老口诀:"不要用 NOT IN,用 NOT EXISTS"。原因是 NOT IN 的 NULL 语义:只要子查询结果里有一个 NULL,整个 NOT IN 就返回空——优化器因此不敢把它转成 ANTI JOIN,只能走低效的 hashed SubPlan 甚至逐行 SubPlan。
PG 19 的做法务实:当优化器能证明两侧都不含 NULL 时(比如列上有 NOT NULL 约束),NOT IN 自动转 ANTI JOIN:
-- 假设 orders.customer_id 和 blacklist.customer_id 都是 NOT NULL
EXPLAIN (COSTS OFF)
SELECT * FROM orders o
WHERE o.customer_id NOT IN (SELECT b.customer_id FROM blacklist b);
-- PG 18:
-- Seq Scan on orders o
-- Filter: (NOT (hashed SubPlan 1))
-- SubPlan 1
-- -> Seq Scan on blacklist b
-- PG 19:
-- Hash Anti Join
-- Hash Cond: (o.customer_id = b.customer_id)
-- -> Seq Scan on orders o
-- -> Hash
-- -> Seq Scan on blacklist b
Anti Join 可以用 hash、可以并行、内表唯一时还能吃到 PG 19 新增的 Memoize for ANTI JOIN 优化。对于百万级以上的大表反连接,这是数量级的差距。
工程启示:给列加 NOT NULL 约束从来不只是数据质量问题,它直接决定优化器敢做哪些变换。PG 19 之后,这条原则的收益又变大了。
3.2 预聚合下推(Eager Aggregation)
"Allow some aggregate processing to be performed before joins"——聚合可以在 JOIN 之前部分执行。这是学术界叫 eager aggregation 的经典优化,商业库(Oracle、SQL Server)早就有,PG 现在补上了。
典型受益场景:
-- 订单明细表 1 亿行,客户表 100 万行
SELECT c.region, SUM(o.amount)
FROM orders o JOIN customers c ON o.customer_id = c.id
GROUP BY c.region;
传统计划:先 JOIN(1 亿行 × 探测),再对 1 亿行分组聚合。
Eager Aggregation:先按 o.customer_id 对 orders 做部分聚合(1 亿行 → 100 万组),再 JOIN,再做最终聚合。中间结果集直接缩小两个数量级。
优化器会基于代价决定是否启用,不需要改 SQL。这对报表类查询是"免费的午餐"。
3.3 表达式化简全家桶
一组看似琐碎、实则很实用的常量折叠增强:
x IS DISTINCT FROM NULL→ 化简为x IS NOT NULL(后者能走索引、能用于分区裁剪)- 输入可证明非空时,
IS DISTINCT FROM直接化简为=/<> COALESCE()和ROW(...) IS NULL跳过不必要的参数求值IS TRUE/FALSE/UNKNOWN在非空输入下化简为普通布尔表达式
这类化简单独看每条收益都不大,但 ORM 生成的 SQL 里充斥着 IS DISTINCT FROM 这类写法(比如 Hibernate 的 null-safe 比较),叠加起来对真实负载的改善相当可观。
四、性能:Autovacuum 并行化是最大的隐形红利
4.1 并行 Autovacuum Worker
PG 19 允许 autovacuum 使用并行 worker 清理单张表,由两个参数控制:
-- 全局上限
autovacuum_max_parallel_workers = 4
-- 按表设置
ALTER TABLE big_events SET (autovacuum_parallel_workers = 4);
在此之前,手动 VACUUM (PARALLEL 4) 早就支持并行处理索引,但 autovacuum 一直是单 worker 干到底。对于有十几个索引的大表,autovacuum 一轮跑几个小时是常态,跑不完就积累死元组、膨胀、事务 ID 回卷风险层层加码。
这个特性的正确打开方式:
- 不要全局无脑开。并行 worker 消耗的是共享的
max_parallel_workers池子,和查询并行抢资源。 - 只给"大表 + 多索引 + 高更新频率"的表开。用 PG 19 新增的
pg_stat_autovacuum_scores视图(下文详述)找出 autovacuum 压力最大的表,精准投放。 - 配合
vacuum_cost_limit调整,避免并行化之后 I/O 冲击翻倍。
4.2 SIMD 加速 COPY FROM
COPY FROM 的 text/CSV 解析路径用上了 SIMD 指令。CSV 解析的核心热点是逐字节扫描分隔符、引号、换行符,天然适合向量化——一条指令同时检查 16/32 个字节。
对 ETL 和数据迁移场景,这意味着大文件导入的 CPU 瓶颈被明显缓解。结合 PG 17 引入的 COPY ... ON_ERROR ignore 和 PG 19 的这次加速,PostgreSQL 的批量导入能力已经相当能打。实测千万行级 CSV 导入,Beta 2 相比 PG 18 有 15%-30% 的提升(取决于列数和字段宽度)。
4.3 AIO Worker 自动伸缩
PG 18 引入的异步 I/O(io_method = worker)需要手动指定 worker 数量(io_workers),配少了 I/O 排队,配多了浪费。PG 19 让 worker 数量自动伸缩:
io_min_workers = 1 # 下限
io_max_workers = 8 # 上限
io_worker_idle_timeout = 60s # 空闲回收
io_worker_launch_interval = 500ms # 扩容节流
这是 AIO 走向成熟的标志——从"能用"到"不用管"。配合本版本对大请求 read-ahead 调度的改进,顺序扫描大表的吞吐进一步提升。
4.4 其他值得一提的性能项
- NOTIFY 精准唤醒:之前一个 NOTIFY 会唤醒几乎所有 backend,现在只唤醒真正 LISTEN 对应 channel 的连接。重度使用 LISTEN/NOTIFY 做消息分发的架构(比如用 PG 当轻量消息队列)在高连接数下收益巨大。
- 普通查询也能设置可见性位图:以前只有 VACUUM 和
COPY FREEZE能把页标记为 all-visible,现在表扫描也可以顺手标记,Index Only Scan 的命中率会更稳定。 - 基数排序(radix sort):排序路径引入 radix sort,整数/时间戳类键的大规模排序更快。
- 外键约束检查提速:批量 DML 在有外键的表上开销下降。
- hash 索引批量删除和 GIN 索引 vacuum 用上流式读取(streaming reads),继续吃 AIO 的红利。
五、可观测性:三个新系统视图,DBA 的体检报告更全了
5.1 pg_stat_lock:锁统计终于有了累计视角
pg_locks 只能看当前时刻的锁快照,想知道"过去一周哪类锁等待最严重"只能靠外部采样。PG 19 新增 pg_stat_lock 视图,按锁类型累计统计:
SELECT * FROM pg_stat_lock ORDER BY wait_count DESC;
排查锁争用问题从"蹲点抓现场"变成"直接看账本"。
5.2 pg_stat_autovacuum_scores:autovacuum 决策透明化
autovacuum 为什么迟迟不清理某张表?以前你需要手动套公式算阈值。现在直接查:
SELECT relname, score, threshold, dead_tuples
FROM pg_stat_autovacuum_scores
ORDER BY score DESC LIMIT 20;
哪些表快要触发、哪些表长期"欠费",一目了然。配合前述的并行 autovacuum 参数,构成了完整的"发现问题 → 精准治理"闭环。
5.3 pg_stat_recovery 与更细的进度报告
pg_stat_recovery:备库恢复状态一站式查询。pg_stat_progress_vacuum新增started_by(谁发起的:autovacuum/手动/回卷保护)和mode(激进程度)。pg_stat_wal增加 full page image 写入字节数——FPI 是 WAL 膨胀的头号元凶,现在可以量化监控了。- 逻辑复制侧:
pg_stat_replication_slots新增mem_exceeded_count(logical_decoding_work_mem 超限次数),slot 同步跳过原因也有了专门的列。调逻辑复制内存参数终于有数据支撑。
六、升级实战:一份可执行的 checklist
假设你计划在 PG 19 GA 后 3-6 个月内升级生产,现在(Beta 阶段)就可以做这些事:
第一步:兼容性扫描(现在就能做)
-- 1. 检查是否有依赖 standard_conforming_strings = off 的配置
SELECT name, setting FROM pg_settings
WHERE name IN ('standard_conforming_strings', 'escape_string_warning');
-- 2. 检查 btree_gist 的 inet/cidr 索引
SELECT indexrelid::regclass, indrelid::regclass
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
JOIN pg_am am ON am.oid = c.relam
WHERE am.amname = 'gist'
AND EXISTS (
SELECT 1 FROM pg_attribute a
WHERE a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
AND a.atttypid IN ('inet'::regtype, 'cidr'::regtype)
);
-- 3. 检查含换行/回车的对象名
SELECT datname FROM pg_database WHERE datname ~ '[\n\r]';
SELECT rolname FROM pg_roles WHERE rolname ~ '[\n\r]';
-- 4. 检查是否还在用 MD5 密码
SELECT rolname FROM pg_authid WHERE rolpassword LIKE 'md5%';
第二步:参数迁移对照
| 参数 | PG 18 | PG 19 | 动作 |
|---|---|---|---|
| jit | on | off | 分析型负载手动开启 |
| max_locks_per_transaction | 64 | 128(容量语义减半) | 显式设置值 ×2 |
| default_toast_compression | pglz | lz4 | 一般无需动作 |
| io_workers | 固定值 | min/max 自动伸缩 | 改用新参数组 |
第三步:Beta 环境回归
用 pg_upgrade --check 先做干跑;重点回归 NOT IN 类查询的计划变化(大概率变好,但要确认)、LISTEN/NOTIFY 行为、以及 json_array() 空结果的语义变化。
第四步:灰度顺序建议
备库先升(利用 pg_stat_recovery 观察恢复状态)→ 只读流量验证 → 主库切换。逻辑复制用户注意 pg_stat_subscription_stats 的列名变更(sync_error_count → sync_table_error_count),监控面板的 SQL 要同步改。
七、总结与展望
PG 19 不是一个靠单一大特性撑门面的版本,它的价值在于三条线的齐头并进:
- 诚实:承认 JIT 代价模型不可靠就默认关掉,承认 RADIUS over UDP 修不好就删掉,承认 btree_gist 的 inet opclass 有正确性 bug 就换掉。一个 30 岁的开源项目还有勇气做减法,这比任何新特性都珍贵。
- 补课:NOT IN 转 ANTI JOIN、eager aggregation、radix sort——这些商业数据库的存量优化被逐个补齐,配合 NOT NULL 约束的普及,PG 优化器和商业库的差距肉眼可见地在缩小。
- 自治:并行 autovacuum、AIO worker 自动伸缩、autovacuum 决策透明化——数据库正在朝"少人工干预"的方向演进,这也是 AI 时代对基础设施的隐含要求:让 Agent 和自动化工具能通过系统视图理解数据库的健康状态,而不是依赖 DBA 的经验直觉。
按社区节奏,PG 19 正式版预计 2026 年 9 月底发布。现在正是把 Beta 2 拉进测试环境的最佳时机——尤其是分区大表用户、逻辑复制用户和还开着 JIT 跑 OLTP 的用户,越早测,升级窗口越从容。
数据库这个行业没有魔法,只有一个个正确性 bug 的修复、一次次代价模型的校准和三十年如一日的 review 纪律。PG 19 是这种工程文化的又一次例行输出,而这恰恰是它最值得学习的地方。