PostgreSQL 18 深度解析:异步 I/O、skip scan 与一次更省心的升级

2025 年 9 月 25 日,PostgreSQL 18 正式发布。这是近年改动面最宽的一个大版本:读路径迎来多年来的第一次架构级重构——异步 I/O;优化器补上 skip scan 和一批规则改写;开发者侧新增虚拟生成列、uuidv7() 与 RETURNING 新旧值双取;pg_upgrade 第一次把优化器统计搬进新集群。本文所有特性描述均以 postgresql.org 官方文档为准,按「解决什么问题、怎么用、对升级有什么影响」逐项展开,最后给一份可执行的升级建议。

总览:18 版改了什么

PostgreSQL 每年一个 major 版本,18 的特别之处在于「底层动刀」与「日常省心」同时发生。先看与生产关系最大的特性全景:

特性 解决的问题 用法要点
异步 I/O 后端读数据页要逐块同步等待,云盘延迟被放大 io_method = worker(默认)或 io_uring,io_workers 默认 3
skip scan 复合索引前导列无条件时整条索引用不上 尾列等值、前导列低基数收益最大,由 planner 按代价选择
虚拟生成列 STORED 列写入时计算,占存储、拖慢写入 新建生成列默认 VIRTUAL,表达式限内置函数
uuidv7() 随机 UUID 主键索引局部性差 毫秒时间戳有序,可传 interval 偏移
pg_upgrade 保留统计 升级后统计清空,执行计划赌运气 默认转移,扩展统计需 vacuumdb 补齐
RETURNING OLD/NEW 改动前后值要查两次或靠触发器 RETURNING old.col, new.col,别名可改
时间约束 时段重叠校验散落在触发器与代码 PRIMARY KEY (room, during WITHOUT OVERLAPS)
OAuth 2.0 认证 企业身份体系接入要靠外部插件 pg_hba.conf 新增 oauth 方法

还有几件容易忽略的事:initdb 默认开启数据校验和;autovacuum_max_workers 不再需要重启即可调整;订阅端并行流式回放从默认关闭改为默认开启;MD5 口令认证开始给出弃用警告。下面挑对生产影响最大的部分展开。

异步 I/O:读路径的架构级拐点

长期以来,PostgreSQL 的每个后端进程都按「一次一个系统调用」的方式读数据:读完一块、处理、再读下一块。PostgreSQL 17 引入 read stream 统一了预读的发起方式,但本质仍是靠 posix_fadvise 提示内核预热页缓存——后端依旧要为每一块数据同步等一次系统调用。18 把 I/O 层重写为异步模型:后端把一批读请求排队提交,I/O 在后台完成,后端继续扫描与计算。官方公告称该子系统在读场景最高带来 3 倍性能提升,官方 Press Kit 的口径同样是「某些场景最高 3 倍」,skip scan 则没有任何官方数字;pganalyze 在 AWS EBS(2 万 IOPS)上的冷缓存实测里,对 3.5 GB 表全表 COUNT:PG17 约 15.8 秒,PG18 的 worker 模式约 10.1 秒,io_uring 模式约 5.7 秒——worker 与 io_uring 相对同步读稳定落在 2–3 倍区间,云盘等高延迟存储收益最大,本地 NVMe 提升有限。

用法上一条配置决定一切,io_method 取三个值(重启生效):

io_method = worker   # 默认值,通用性最好,后台 I/O worker 进程代为预取
io_workers = 3       # 仅 worker 模式生效,默认 3,高 IOPS 存储可调大,可 reload
io_combine_limit = 128kB   # 单次合并 I/O 大小,调大要同时改 io_max_combine_limit

选 io_uring 走 Linux 内核的异步队列,开销最小,但要求编译时带 --with-liburing 且内核支持(一般要求 5.1 以上);sync 则退回 17 的行为,等于关掉 AIO。配套还有两处变化:effective_io_concurrency 语义升级为直接控制预读深度,默认值从 1 提到 16,且在缺乏 fadvise 的系统上也能设为大于 0;新增 pg_aios 视图可以观察在途的异步请求。

flowchart LR
    BE[后端进程] -->|排队提交读请求| AIO[AIO 层 io_method]
    AIO -->|worker| WK[I/O worker 进程]
    AIO -->|io_uring| UR[Linux 内核 io_uring]
    WK --> SB[(shared buffers)]
    UR --> SB
    SB --> SCAN[顺序扫描、位图堆扫描与 VACUUM]

两点升级提醒。其一,AIO 目前只覆盖读:顺序扫描、位图堆扫描与 VACUUM 等,写路径仍是同步。其二,可观测性语义变了:worker 模式下后端会出现新的 AIO 等待事件,io_uring 的活动在 pg_stat_activity 里不可见、要用 pg_aios 看;pganalyze 还观察到 EXPLAIN ANALYZE 的 I/O 计时在 AIO 下可能低估实际工作量,容量评估建议以 buffer 计数为主。

小结:AIO 是这版最值得升级的理由,读重的云上实例几乎白拿性能,但要把监控面板与压测口径跟着换一套。

skip scan:复合索引的「跳读」能力

在此之前,B-tree 复合索引的规则近乎铁律:前导列必须有等值条件,因为索引按第一列排序,绕开第一列的约束就只能全索引扫。18 允许优化器对复合 B-tree 索引做 skip scan——当前导列没有等值条件(或只有非等值条件)、而后面的列有可用约束时,扫描器为前导列动态生成等值约束,按每个可能取值「跳着」检索索引,官方文档的例子如下:

CREATE INDEX ON orders (customer_id, status);
-- 18 之前:前导列 customer_id 无条件,只能顺序扫描
-- 18 起:planner 可以对 status = 'paid' 走 skip scan
SELECT * FROM orders WHERE status = 'paid';

它不是免费的午餐:planner 只有在「前导列 distinct 值足够少、期望能跳过大部分叶子页」时才选这条路;前导列基数极大时,skip scan 退化成全索引扫,多数情况不如顺序扫。它还能在多列约束的场景局部生效——例如 WHERE a = 5 AND b >= 42 AND c < 77,扫描会在 a、b 的分组内对 c 跳读。

对升级的实操含义:以前为了这类查询专门「反转」索引列序、或再造一条窄索引的调优套路,18 之后可以先按业务直觉建复合索引,让 planner 自己决定怎么扫。经验法则不变的部分是:尾列等值、前导列低基数的组合收益最大;命中与否由代价模型说了算,上线前后都要拿真实数据 EXPLAIN 验证,不要指望它对所有的「缺前导列」查询都生效。

小结:skip scan 减少「建索引还得伺候查询写法」的心智负担,本质是 planner 在更多索引形态上有了可用计划。

开发者体验:虚拟生成列、uuidv7 与 RETURNING 双值

虚拟生成列:生成列此前只有 STORED 一种——写入时算好、像普通列一样占存储。18 把 VIRTUAL 变成默认:列不占空间,读取时计算,写密集的表可以放心加派生列而不付写放大:

CREATE TABLE people (
  height_cm numeric,
  height_in numeric GENERATED ALWAYS AS (height_cm / 2.54)  -- 默认 virtual
);

限制要记牢:虚拟列的表达式只能用内置函数与内置类型,不能引用用户自定义函数(STORED 列无此限制);逻辑复制新增的 publish_generated_columns 参数目前也只支持 STORED 列。旧版本迁来的 STORED 列语义不变,无升级风险。

uuidv7():随机 UUIDv4 做主键的经典问题是索引局部性差、缓存命中率低、页分裂频繁。RFC 9562 的 UUIDv7 把毫秒级 UNIX 时间戳放进高位、辅以亚毫秒计数与随机位,取值天然按时间有序。18 将其内置,还可选 interval 参数做时间偏移(偏出 48 位时间戳范围会报错),并给随机版本补了显式别名 uuidv4():

SELECT uuidv7();                          -- 019535d9-3df7-79fb-b466-fa907fa17f9e
SELECT uuid_extract_timestamp(uuidv7());  -- 从 UUID 里还原生成时间

RETURNING 新旧双值:INSERT/UPDATE/DELETE/MERGE 现在可以在 RETURNING 里同时取改前与改后的值,别名可自定义:

UPDATE accounts SET balance = balance - 100
WHERE id = 42
RETURNING old.balance AS before, new.balance AS after;

审计、行级 diff 这类过去要「先读后写」或塞给触发器的场景,一条语句搞定。另外 18 支持 WITHOUT OVERLAPS 时间约束与 PERIOD 外键,预约不重叠这类校验从应用与触发器下沉为声明式约束。

小结:这一组特性都指向同一件事——把常见应用层仪式收进数据库一条 SQL 里,升级即得,几乎无学习成本。

升级实操:统计保留、--swap 与行为变化

大版本升级最疼的两点是停机窗口和升级后的执行计划抖动,18 在两点上都有实质改善。pg_upgrade 现在默认把旧集群的优化器统计转移进新集群——升级完的计划立刻是「有统计的」。有三类数据不在转移范围:CREATE STATISTICS 建的扩展统计、扩展自定义的统计、累计统计系统(pg_stat 系列表)的数据,升级后补一次即可:

vacuumdb --all --analyze-in-stages --missing-stats-only   # 只给缺统计的关系补最小统计
vacuumdb --all --analyze-only                             # 完整分析,可配 --jobs 并行

传输模式上 18 新增 --swap:直接交换新旧数据目录、再把 catalog 文件替换为新版,官方文档称在关系数多的集群上快于 --link/--clone/--copy。代价是旧目录被破坏性修改,文件传输一旦开始就无法回退启动旧库,务必先有备份并用 --check 完整演练;--jobs 可并行处理多个数据库与表空间,官方建议起步设为 CPU 核数。

社区升级指南里反复出现的拦路虎是扩展兼容:每个已安装扩展都要提供新 major 版本可用的 so 动态库与 UPDATE 脚本,否则 pg_upgrade 的前置检查直接失败;遗留的 fdw/外部服务器等对象也可能让检查环节卡住。等配环境把全部扩展装齐、功能过一遍,是演练不可省的一步。

升级清单上还有几个行为变化要逐条过:

  • initdb 默认开启数据校验和(针对新集群,想关用 --no-data-checksums),校验和的写开销要重新纳入容量评估。
  • 监控面板注意:pg_stat_io 新增 read_bytes/write_bytes/extend_bytes 并删除 op_bytes 列;部分 WAL 读写统计从 pg_stat_wal 迁入 pg_stat_io,旧面板会直接断列。
  • effective_io_concurrency 默认值从 1 变 16,升级后预读行为改变,I/O 敏感实例应压测确认。
  • MD5 口令认证开始输出弃用警告(md5_password_warnings 控制),尽快迁 SCRAM。
  • autovacuum_max_workers 改为运行期可调(配套 autovacuum_worker_slots),高峰扩容不再要重启。
  • 复制槽新增 idle_replication_slot_timeout,闲置槽自动失效,给 WAL 膨胀兜底。

小结:统计保留 + --swap 把升级窗口和升级后调优两头的成本都砍了一截,但监控断列与默认值变化要求升级演练里包含「看板过一遍」。

监控与安全补遗

如果只关心运维,这几项值得单独一提。可观测性:EXPLAIN ANALYZE 默认包含 BUFFERS,扫描节点显示索引查找次数,Materialize/WindowAgg/CTE 节点显示内存与磁盘占用;pg_stat_all_tables 新增 vacuum 与 analyze 的累计耗时四列;pg_stat_get_backend_io() 与 pg_stat_get_backend_wal() 支持按后端排查 I/O 与 WAL;log_connections 细化到连接各阶段并输出耗时,log_line_prefix 新增 %L 输出客户端 IP。安全:OAuth 2.0 认证进入核心(校验经 oauth_validator_libraries 挂载扩展,需 --with-libcurl 编译);TLS 侧 ssl_groups 取代 ssl_ecdh_curve 且默认 X25519,新增 ssl_tls13_ciphers;线路协议升级到 3.2——2003 年(7.4 版)以来首次大版本变更,取消请求密钥升到 256 位。优化器还有一批「白捡」的改写:自连接消除、OR 条件转数组比较、SELECT DISTINCT 键重排、GROUP BY 冗余列删除,都是升级后自动生效。

小结:监控侧的改动密度不亚于性能侧,升级公告值得逐行读完再动生产。

升级建议

截至 2026-10-10,18 已发布约一年,主流扩展兼容性基本就绪。给出如下顺序建议:

  • 评估收益排序:云盘/网络存储、大表顺序扫描与 VACUUM 繁重的实例收益最大,优先升;本地 NVMe 且 I/O 不饱和的实例按维护窗口从容安排。
  • 预演:pg_upgrade --check 配合 --swap 在等配机器上完整走一遍,确认旧目录破坏性变更的回退预案,备份永远优先。
  • 依赖盘点:逐条确认扩展的新版 so 与 UPDATE 脚本是否齐备,再过虚拟生成列的内置函数限制、pg_stat_io 列变更、MD5 弃用警告、OpenSSL 1.1.1 与 LLVM 14 编译要求。
  • 升级后:先跑 vacuumdb --missing-stats-only,用 EXPLAIN 确认既有慢查询是否吃到 skip scan 与优化器改写红利,再把 io_method 从 worker 试到 io_uring。
  • 灰度观察:AIO 下 EXPLAIN 的 I/O 计时偏乐观,观察期以 pg_aios 与 buffer 计数为准,数字回归正常后再下结论。

总体判断:18 是一次「底层重写、上层无痛」的升级——异步 I/O 与 skip scan 值得为它专门排一次升级窗口,而统计保留让这次升级比以往任何一版都更省心。

参考资料

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?