PostgreSQL 18 深度拆解:当世界上最先进的开源数据库重构 I/O 底座——从异步 I/O 子系统、UUIDv7 时间有序主键到虚拟生成列与 MERGE RETURNING 的工程全貌(2026)
如果说过去几个大版本里 PostgreSQL 一直在「加功能」,那么 PostgreSQL 18 的基调是「夯地基」。异步 I/O(AIO)子系统的落地,是 PostgreSQL 内核二十多年来第一次真正把磁盘读取从「同步阻塞」里解放出来;与此同时,UUIDv7 原生支持、虚拟生成列、MERGE ... RETURNING、OAuth 2.0 鉴权,又让它在分布式主键、存储成本、变更捕获和企业 SSO 这几个实战痛点上补齐了关键拼图。本文从工程视角,把 PG18 的底层改动、架构权衡和可在生产直接落地的代码实战一次性讲透。
一、背景:为什么 PostgreSQL 18 是一次「底层重构」而非功能堆叠
很多团队对 PostgreSQL 的印象还停留在「功能多、稳定、但大表扫描慢」。这个刻板印象在 PG18 之前是有客观原因的:传统 PostgreSQL 的读取路径是同步阻塞的。后端进程(backend)要从堆表读一个页面,就调用 read()/pread(),然后整个进程被挂起,直到操作系统把数据从磁盘搬进内核缓冲区、再拷贝到用户态。CPU 在等待 I/O 的这段时间只能空转——监控上表现为那条刺眼的 iowait。
云时代这个问题被放大了。本地 NVMe 的单次 4K 随机读延迟在 100μs 量级,而云盘(即便是最快的云 SSD)的单次阻塞读往往要 500μs–数 ms,且高并发下抖动剧烈。PostgreSQL 过去依赖操作系统的 posix_fadvise 建议性预读,但操作系统并不知道你下一步要做全表扫描还是索引范围扫描,预读的命中率有限,经常「白忙活」。
PG18 的解法是:数据库自己接管 I/O 调度。它引入了一套全新的异步 I/O 子系统,让后端进程在发起读请求后不必等待,转身去处理已经进内存的数据或规划下一个 I/O,把 CPU 计算和磁盘 I/O 在时间轴上重叠成一条流水线。这背后不是改一个参数,而是对 smgr(存储管理器)接口、ReadStream 设施、缓冲区管理器的重新设计——是一次货真价实的底层重构。
围绕这条主线,PG18 还在「标识、存储、变更、接入」四个维度补齐了长期被社区催更的能力。下面逐一拆解。
二、核心概念速览
| 能力 | 一句话定位 | 实战价值 |
|---|---|---|
| 异步 I/O(AIO) | 用 io_method=worker/io_uring 把读请求异步化 | 顺序扫描 / VACUUM / 位图堆扫描提速 2–3 倍 |
| UUIDv7 原生函数 | 时间戳有序的 UUID,告别 UUIDv4 的随机写入放大 | 主键插入无 B-tree 分裂抖动,索引更紧凑 |
| 虚拟生成列 | GENERATED ALWAYS AS (expr) VIRTUAL,查询时计算 | 省磁盘、免触发器,逻辑与数据同 schema 管理 |
| MERGE ... RETURNING | 合并写操作后直接返回 OLD/NEW 行 | 一行 SQL 完成 CDC / 审计,无需触发器 |
| OAuth 2.0 鉴权 | 支持 OAUTHBEARER SASL,对接企业 SSO | 统一身份、短令牌、免改应用密码体系 |
| 更顺滑的大版本升级 | pg_upgrade 提速 + 克隆式文件复制 | 停机窗口显著缩短,升级后更快达到预期性能 |
三、架构分析
3.1 异步 I/O(AIO)子系统:从阻塞等待到并行流水线
PG18 的 AIO 不是简单套一层 libaio,而是一套分层设计:
- smgr 接口扩展:新增
smgr_startreadv等异步读取方法,并引入PgAioTargetInfo与PgAioHandleCallBacks回调结构。一次异步读 = 提交请求 → 内核/worker 执行 → 回调通知完成。 - ReadStream 设施:顺序扫描、位图堆扫描、VACUUM 现在通过「读取流」抽象发起批量、可合并、可乱序完成的 I/O。多个相邻块会被
io_combine_limit合并成一次大 I/O(默认 256KB 上限)。 - I/O worker 池:当
io_method=worker时,一组后台 worker 进程从共享内存队列取读请求、执行pread、把数据填进共享缓冲区、再唤醒等待的后端。这把「谁来做 I/O」和「谁用数据」解耦了。
io_method 有三个取值,对应不同的生产取舍:
sync(向后兼容):仍然同步 I/O,但辅以posix_fadvice的预取,数据进页缓存而非共享缓冲区。worker(默认):独立 worker 进程池执行预读,绕开后端阻塞,对云盘高延迟场景收益最明显。io_uring:在支持的 Linux 内核上用io_uring做真正的异步提交,基准测试里顺序读可翻倍。需要内核与编译选项支持,生产启用前要充分压测。
重要边界:PG18 的 AIO 目前只覆盖「读」。WAL 写入、脏页刷盘(BufferFileWrite)仍是同步的——这是有意为之,避免一次改动引入过多不确定风险。写路径的异步化是后续版本的方向。
3.2 UUIDv7:把主键变成「时间有序」的索引友好型 ID
UUIDv4 最大的隐性成本不是「不唯一」,而是「太随机」。用 UUIDv4 当主键,每一行插入都落到一个几乎随机的 B-tree 位置,导致:
- 索引页频繁分裂,填充率上不去;
- 插入热点打散到全表,缓存局部性差,SSD 写入放大严重;
- 范围扫描(按时间取最近 N 条)几乎用不上索引有序性。
UUIDv7(RFC 9562 method 1)把 48 位毫秒级 Unix 时间戳 放在高位,低位再补随机位。于是 UUID 在「生成顺序」上天然与时间相关——新插入的行在 B-tree 里天然相邻,插入变成了「顺序追加」式的局部写入,索引更紧凑、缓存命中更高。PG18 直接内置 uuidv7() 函数,无需 uuid-ossp 扩展。
3.3 虚拟生成列:查询时计算,省下磁盘与维护
PostgreSQL 12 就引入了生成列,但只有 STORED(计算结果落盘)。PG18 新增 VIRTUAL:结果不落盘,查询时实时计算,默认就是 VIRTUAL。它用数据库内核统一管理派生数据,替代了过去靠视图、触发器或应用层重复写表达式的做法,让 schema 逻辑更干净。
需要注意的边界:虚拟列不占磁盘,但不能直接建 B-tree 索引(因为没有持久化值)。如果某派生列需要被索引加速,要么改用 STORED,要么建一个等价的函数索引(functional index)。
3.4 MERGE ... RETURNING:变更即捕获
PG18 给 MERGE 增加了 RETURNING 子句,并引入 OLD / NEW 别名(提交 80feb727c8)。这让「upsert 之后立刻知道哪行被插入、哪行被更新、更新前后的值是什么」成为一条 SQL 的事,对审计日志、CDC(变更数据捕获)、幂等写入的回执都极有价值。
3.5 OAuth 2.0 鉴权与更顺滑的大版本升级
PG18 加入 OAuth 2.0 认证(基于 OAUTHBEARER SASL),可对接企业 SSO,用短期令牌代替长期密码,安全运维更顺手。升级侧,pg_upgrade 更快,并通过 file_copy_method = clone(依赖 XFS/Btrfs 的 reflink 写时复制)实现毫秒级、近零冗余的物理文件复制;同时 PG18 显著缩短了「升级完成后达到预期性能」的预热时间,停机窗口更可控。
四、代码实战
4.1 开启并验证异步 I/O
postgresql.conf 关键配置(生产建议从 worker 起步):
# - Asynchronous I/O -
io_method = worker # sync | worker | io_uring
io_workers = 4 # I/O worker 进程数,默认 3,高并发读可适当上调
# 与 AIO 协同的预读/合并参数
effective_io_concurrency = 300 # 单个查询可并行发起的 I/O 请求数
maintenance_io_concurrency = 300 # VACUUM / CREATE INDEX 等的并发 I/O
io_combine_limit = 256kB # 相邻块合并上限,减少小 I/O 次数
查看当前 AIO 设置与正在飞行的 I/O:
-- 当前 io_method / io_workers
SHOW io_method;
SHOW io_workers;
-- PG18 新增:实时监控「在途」的异步 I/O 请求
SELECT * FROM pg_aios;
压测对比(顺序扫描大表的典型收益):
-- 制造一张 1 亿行的大表
CREATE TABLE bench AS
SELECT g AS id, repeat('x', 200) AS payload
FROM generate_series(1, 100000000) g;
-- 关闭 AIO(io_method=sync)时
EXPLAIN (ANALYZE, BUFFERS) SELECT count(*) FROM bench;
-- 典型表现:大量 Buffers: read=...,执行时间长,iowait 高
-- 切到 io_method=worker 并重启后
EXPLAIN (ANALYZE, BUFFERS) SELECT count(*) FROM bench;
-- 读密集场景往往可见 2–3 倍吞吐提升,iowait 显著下降
工程提醒:
io_uring模式虽然最快,但依赖内核版本与编译选项,且历史上io_uring有过 CVE。生产环境优先worker,并在灰度集群压测后再决策。
4.2 UUIDv7 主键设计
-- PG18 原生支持 uuidv7()
CREATE TABLE orders (
id uuid PRIMARY KEY DEFAULT uuidv7(),
user_id bigint NOT NULL,
amount numeric(12,2) NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO orders (user_id, amount) VALUES
(1001, 19.90), (1002, 299.00), (1003, 8.50);
-- 新插入的行在 B-tree 中天然按时间相邻,
-- 按时间取「最近 100 单」可直接走主键有序扫描
SELECT id, user_id, amount
FROM orders
ORDER BY id DESC
LIMIT 100;
对比:同样的结构若用 gen_random_uuid()(UUIDv4),插入会打散到索引各处,长时间运行后主键索引的填充率与写入局部性都明显劣于 UUIDv7。
4.3 虚拟生成列:替代触发器与重复表达式
CREATE TABLE users (
id serial PRIMARY KEY,
name text NOT NULL,
-- 存储型:结果落盘,可被索引
name_upper text GENERATED ALWAYS AS (upper(name)) STORED,
-- 虚拟型(PG18 新增,默认):查询时计算,不占磁盘
name_lower text GENERATED ALWAYS AS (lower(name)) VIRTUAL
);
INSERT INTO users (name) VALUES
('Florents'), ('Αθηνά'), ('Postgres');
SELECT id, name, name_upper, name_lower FROM users;
-- 1 | Florents | FLORENTS | florents
-- 2 | Αθηνά | ΑΘΗΝΆ | αθηνά
-- 3 | Postgres | POSTGRES | postgres
-- 几何/财务派生字段同样适用
CREATE TABLE circles (
id integer PRIMARY KEY,
radius numeric NOT NULL,
perimeter numeric GENERATED ALWAYS AS (2 * 3.14159265 * radius) VIRTUAL,
area numeric GENERATED ALWAYS AS (3.14159265 * radius * radius) VIRTUAL
);
需要给虚拟列加速检索时,用函数索引等价替代:
CREATE INDEX idx_users_name_lower ON users (lower(name));
4.4 MERGE ... RETURNING:一行搞定 CDC / 审计
CREATE TABLE inventory (
sku text PRIMARY KEY,
qty integer NOT NULL,
updated_at timestamptz
);
CREATE TABLE inventory_audit (
sku text,
op text,
old_qty integer,
new_qty integer,
at timestamptz DEFAULT now()
);
-- 合并写入:存在则更新,不存在则插入,并同时回写审计
MERGE INTO inventory AS tgt
USING (VALUES ('SKU-A', 50)) AS src(sku, qty)
ON tgt.sku = src.sku
WHEN MATCHED THEN
UPDATE SET qty = tgt.qty + src.qty, updated_at = now()
WHEN NOT MATCHED THEN
INSERT (sku, qty, updated_at) VALUES (src.sku, src.qty, now())
RETURNING
CASE WHEN tgt.qty IS NOT NULL THEN 'UPDATE' ELSE 'INSERT' END AS op,
OLD.qty AS old_qty,
NEW.qty AS new_qty,
NEW.sku;
OLD.qty / NEW.qty 让你在一次语句里拿到变更前后的值,直接落库到 inventory_audit,无需触发器、无需应用层二次查询。
五、性能优化与可观测性
5.1 io_method 选型决策树
- 本地 NVMe、延迟敏感的分析查询:
worker起步,必要时评估io_uring。 - 云盘 / 高延迟块存储、大表报表:
worker收益最稳,务必把effective_io_concurrency调到接近盘的真实并发(云盘常可设到 200–400)。 - 保守 / 兼容性优先:
sync兜底,但会丧失 AIO 带来的预读并行度。
5.2 并发与合并调优
# 经验起点(按实际压测微调)
io_workers = 4
effective_io_concurrency = 300
maintenance_io_concurrency = 300
io_combine_limit = 256kB
调优铁律:先用 pg_aios 观察在途 I/O 是否堆积,再用 EXPLAIN (ANALYZE, BUFFERS) 对比 Buffers: read 与执行时间,避免「拍脑袋调大」。AIO 当前主要利好顺序扫描、位图堆扫描和 VACUUM;写密集、点查为主的负载收益有限。
5.3 大版本升级提速与克隆
# postgresql.conf(目标实例)
file_copy_method = clone # 依赖 XFS/Btrfs reflink,文件复制近零冗余、毫秒级
配合 pg_upgrade --clone 或云平台 reflink 克隆,可在不停机窗口内快速产出可写副本,用于演练、读副本或 AI 场景的数据基建。
5.4 PG18 新增的可观测性视图
pg_stat_all_tables新增 (auto)vacuum / (auto)analyze 的耗时指标,定位「VACUUM 为什么慢」不再靠猜。pg_stat_checkpointer新增num_done,直观看到检查点实际完成情况。pg_stat_database新增parallel_workers_to_launch/parallel_workers_launched,量化并行 worker 的真实利用率。
-- 观察并行 worker 是否被充分拉起
SELECT datname, parallel_workers_to_launch, parallel_workers_launched
FROM pg_stat_database
WHERE datname = current_database();
另外,PG18 的优化器还带来了自连接消除(Self-Join Elimination)与并行 HashRightSemiJoin,对 IN 子查询、自我内连接的分区表查询在计划层面就能省掉大量无效扫描。
六、总结与展望
PostgreSQL 18 的工程意义,不在于又堆了多少新语法,而在于它把过去二十年里最顽固的两个短板——同步 I/O 阻塞和分布式主键友好性——同时补上了。异步 I/O 子系统让读密集负载第一次有了「和硬件并行对话」的能力;UUIDv7 让「用 UUID 当主键」从性能反模式变成最佳实践;虚拟生成列与 MERGE RETURNING 则把派生数据与变更捕获下沉到内核,简化了无数靠触发器和应用层兜底的脏活。
对一线工程师的落地建议:
- 升级优先级高:尤其当你有报表、VACUUM、大表扫描或 UUID 主键场景,PG18 是值得排期的版本。
- AIO 先
worker后io_uring:用pg_aios+EXPLAIN ANALYZE实测,别盲调。 - 主键默认改 UUIDv7:新表直接
DEFAULT uuidv7(),老表迁移用uuidv7()重算或双写过渡。 - 用虚拟列和 MERGE RETURNING 替掉触发器:更少维护面,更清晰的 schema。
可以预见,AIO 的下一步是把**写路径(WAL、脏页刷盘)**也异步化,并推动 io_uring 成为默认;而 UUIDv7 + 向量检索(配合 pgvector 等扩展)会让 PostgreSQL 在 AI 时代继续稳坐「最先进开源数据库」的位置。对于既要 OLTP 稳定、又要顺手做分析与检索的团队来说,PG18 是一次几乎没有理由拒绝的升级。
参考资料:PostgreSQL 18 官方 Release Notes、IvorySQL/Tomas Vondra AIO 调优指南、ITPUB PG18 六大特性解析、各云厂商 PG18 技术博客。本文代码示例基于 PostgreSQL 18 语法,已在等价环境验证核心语法可用性。