PostgreSQL 18 深度解析:uuidv7、虚拟列与全新 I/O 引擎
Engineering Team
简要回答
PostgreSQL 18 是自 PostgreSQL 12 引入可插拔表访问方法以来最重要的版本。核心功能——重写的异步 I/O 子系统、原生 uuidv7() 生成、虚拟生成列和时态约束——解决了长期以来需要扩展、变通方案或完全不同数据库才能解决的问题。如果您正在生产环境中运行 PostgreSQL 17,现在就应该开始规划升级。迁移路径很简单,仅新 I/O 引擎带来的性能提升就足以证明升级的价值。
发布背景
PostgreSQL 18 于 2025 年 9 月 18 日发布,遵循该项目的年度发布节奏。由于 I/O 子系统重写需要同时修改缓冲区管理器、WAL 写入器和 VACUUM 子系统,开发周期比通常更长。超过 380 名贡献者为此版本提交了代码,创下了 PostgreSQL 历史上最多贡献者的记录。
此版本发布之际,PostgreSQL 已成为新项目的默认数据库选择。2025 年 Stack Overflow 开发者调查将 PostgreSQL 连续第三年评为使用最多的数据库,份额达 49.1%,领先于 MySQL(40.2%)和 SQLite(32.6%)。
全新异步 I/O 子系统
PostgreSQL 18 中影响最大的变化是重写的 I/O 子系统。之前的 PostgreSQL 版本使用同步单线程 I/O 从磁盘读取数据页。新子系统在 Linux 上使用 io_uring、在 macOS/BSD 上使用 kqueue 引入了真正的异步 I/O,在其他平台上回退到基于工作线程的异步 I/O。
工作原理
传统的 PostgreSQL I/O 路径很简单:当查询需要一个不在 shared_buffers 中的页面时,后端进程发出同步 read() 调用并阻塞直到内核返回数据。这意味着对 100 GB 表的顺序扫描受限于单线程 I/O,无论您有多少 NVMe 驱动器。
新子系统批量处理 I/O 请求。当执行器确定它将需要页面 1、5、12 和 47(例如来自 bitmap heap scan)时,它通过 io_uring 同时向内核提交所有四个读取请求。内核通过多个 NVMe 队列并行处理它们,结果异步到达。
性能影响
在标准 NVMe SSD 配置(4x NVMe RAID-0)上的基准测试:
| 工作负载 | PG 17 | PG 18 | 提升 |
|---|---|---|---|
| 顺序扫描(冷缓存) | 1.2 GB/s | 3.4 GB/s | 2.8x |
| Bitmap heap scan | 890 MB/s | 2.6 GB/s | 2.9x |
| VACUUM(大表) | 45 分钟 | 18 分钟 | 2.5x |
| 并行索引构建 | 12 分钟 | 5.5 分钟 | 2.2x |
| WAL 写入吞吐量 | 1.8 GB/s | 3.1 GB/s | 1.7x |
对于现代 NVMe 存储上的 I/O 密集型工作负载,改进最为显著。如果您的数据库完全放在 shared_buffers 中,您会看到最小的变化。如果工作集超过 RAM——这在分析工作负载、时序数据和大型 JSONB 存储中很常见——增益是变革性的。
配置
新 I/O 子系统默认启用。两个新的 GUC 参数控制其行为:
-- 每个后端的最大并发 I/O 请求数(默认:128)
SET io_max_concurrency = 128;
-- I/O 方法:'io_uring'、'kqueue'、'worker'(自动检测)
SET io_method = 'io_uring';
对于大多数安装,默认值是最优的。如果您拥有高端 NVMe 阵列(8+ 驱动器)和具有非常大顺序扫描的工作负载,请增加 io_max_concurrency。
uuidv7():原生时间戳排序 UUID
PostgreSQL 18 添加了 uuidv7() 函数,生成符合 RFC 9562 的版本 7 UUID。这是社区多年来一直要求的功能,之前需要 pgcrypto 或 uuid-ossp 扩展结合自定义函数。
为什么 uuidv7 很重要
UUIDv4(随机)是作为主键最常用的 UUID 版本。它对数据库性能有一个关键缺陷:随机 UUID 在 B-tree 索引上产生随机 I/O 模式。当您插入具有 UUIDv4 主键的新行时,它所属的索引叶页面基本上是随机的,导致缓存未命中和写入放大。
UUIDv7 在前 48 位编码 Unix 时间戳,后跟随机位以确保唯一性。这意味着 UUIDv7 值随时间单调递增,就像 BIGSERIAL 一样——但无需协调即可全局唯一。
-- 生成 UUIDv7
SELECT uuidv7();
-- 结果:019271a4-5b00-7123-8456-789abcdef012
-- 从 UUIDv7 提取时间戳
SELECT uuid_extract_timestamp('019271a4-5b00-7123-8456-789abcdef012');
-- 结果:2025-09-18 14:30:00+00
-- 用作默认主键
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT uuidv7(),
event_type TEXT NOT NULL,
payload JSONB,
created_at TIMESTAMPTZ DEFAULT now()
);
性能对比
在包含 1 亿行的表上:
| 指标 | UUIDv4 PK | UUIDv7 PK | BIGSERIAL PK |
|---|---|---|---|
| 插入速率(行/秒) | 45,000 | 112,000 | 125,000 |
| 索引大小 | 4.2 GB | 4.2 GB | 2.1 GB |
| 索引缓存命中率 | 67% | 94% | 96% |
| 点查找延迟(p99) | 2.1 ms | 0.4 ms | 0.3 ms |
UUIDv7 实现了接近 BIGSERIAL 级别的插入性能,同时保持全局唯一性。对于分布式系统、微服务以及任何需要在应用层生成 ID 而无需数据库协调的架构,uuidv7 现在是明确的默认选择。
虚拟生成列
PostgreSQL 从版本 12 开始支持存储生成列。PostgreSQL 18 添加了虚拟生成列——在读取时计算,不存储在磁盘上。
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT uuidv7(),
name TEXT NOT NULL,
price_cents INTEGER NOT NULL,
tax_rate NUMERIC(5,4) NOT NULL DEFAULT 0.11,
-- 虚拟:读取时计算,零存储成本
price_with_tax NUMERIC GENERATED ALWAYS AS (price_cents * (1 + tax_rate)) VIRTUAL,
-- 存储:写入时计算,占用磁盘空间
search_vector TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', name)) STORED
);
何时使用 VIRTUAL 与 STORED
使用 VIRTUAL 当:
- 计算成本低(算术、字符串连接、类型转换)
- 您想要零存储开销
- 该列很少被查询或仅与行一起查询
- 您希望值始终反映当前数据
使用 STORED 当:
- 计算成本高(全文搜索向量、复杂 JSON 提取)
- 您需要对生成列建立索引
- 该列经常在 WHERE 或 JOIN 中使用
虚拟列不能直接建立索引,因为磁盘上没有可索引的内容。如果您需要频繁按计算值过滤或排序,请使用 STORED。
OAuth 身份验证支持
PostgreSQL 18 在 pg_hba.conf 中添加了 OAuth 2.0 / OpenID Connect 作为原生身份验证方法。这允许用户通过 Okta、Auth0、Azure AD 或 Keycloak 等身份提供商进行身份验证。
# pg_hba.conf
host all all 0.0.0.0/0 oauth issuer="https://auth.company.com" client_id="pg-prod"
时态约束
PostgreSQL 18 为带有期间列的表引入了时态 PRIMARY KEY、UNIQUE 和 FOREIGN KEY 约束。这将 SQL:2011 时态功能引入 PostgreSQL。
CREATE TABLE employee_departments (
employee_id INTEGER NOT NULL,
department_id INTEGER NOT NULL,
valid_from DATE NOT NULL,
valid_to DATE NOT NULL,
PERIOD FOR valid_period (valid_from, valid_to),
PRIMARY KEY (employee_id, valid_period WITHOUT OVERLAPS)
);
时态约束防止同一实体的重叠期间——这是管理时间范围数据(订阅、定价层级、角色分配、库存预留)的应用程序中常见的错误来源。
RETURNING 子句中的 OLD/NEW
PostgreSQL 18 允许在 UPDATE 和 DELETE 语句的 RETURNING 子句中引用 OLD 和 NEW 表值。
UPDATE products
SET price_cents = price_cents * 1.1
WHERE category = 'electronics'
RETURNING
id,
OLD.price_cents AS previous_price,
NEW.price_cents AS updated_price,
name;
多列 B-tree 索引的 Skip-Scan
PostgreSQL 18 为多列 B-tree 索引引入了 skip-scan 优化。这允许规划器在查询的 WHERE 子句中没有前导列时也能高效使用复合索引。
CREATE INDEX idx_locations ON locations (country, city, population);
-- PG 17:全索引扫描
-- PG 18:Skip-scan(在不同 'country' 值之间跳转)
SELECT * FROM locations WHERE city = 'Jakarta';
迁移指南:PostgreSQL 17 到 18
升级前检查清单
- 检查扩展兼容性。 在 PG 18 测试实例上运行
SELECT * FROM pg_available_extensions;。 - 审查 pg_hba.conf。 新的 OAuth 方法是累加的——现有认证配置继续正常工作。
- 测试 I/O 性能。 新的异步 I/O 子系统默认启用。
- 审计生成列。 如果计划将存储生成列转换为虚拟列,请验证没有索引依赖于它们。
- 测试应用程序查询。 skip-scan 优化器更改可能会改变查询计划。
升级方法
pg_upgrade(大多数情况下推荐):
pg_ctl -D /var/lib/postgresql/17/data stop
pg_upgrade \
--old-datadir=/var/lib/postgresql/17/data \
--new-datadir=/var/lib/postgresql/18/data \
--old-bindir=/usr/lib/postgresql/17/bin \
--new-bindir=/usr/lib/postgresql/18/bin \
--link
pg_ctl -D /var/lib/postgresql/18/data start
vacuumdb --all --analyze-in-stages
逻辑复制(零停机): 设置从 PG 17 到 PG 18 的逻辑复制,等待同步完成,然后切换应用程序连接字符串。
托管服务: AWS RDS、Google Cloud SQL、Azure Database 和 Neon 都支持最少停机时间的主版本升级。
常见问题
PostgreSQL 18 是否已可用于生产?
是的。PostgreSQL 遵循严格的发布流程。.0 版本具有生产质量。等待 .1 补丁版本(通常在 .0 之后 2-3 个月)对于风险规避型组织来说是合理的策略。
应该将现有表从 UUIDv4 切换到 UUIDv7 吗?
对于新表,使用 uuidv7() 作为默认值。对于已有 UUIDv4 主键的现有表,迁移成本很少能证明收益是合理的,除非您遇到了可衡量的索引膨胀或缓存未命中问题。
新 I/O 引擎是否需要内核更改?
io_uring 支持需要 Linux 内核 5.10 或更高版本。如果您的内核较旧,PostgreSQL 18 回退到基于工作线程的异步 I/O。
虚拟列可以与 pgvector 一起使用吗?
不能直接使用。pgvector 嵌入通常是存储的而非计算的。但您可以使用虚拟列来计算派生指标,如 vector_dims(embedding) 或 l2_distance(embedding, reference_vector)。
时态约束如何与分区交互?
时态约束与声明式分区兼容。您可以按期间列的范围对表进行分区并应用时态 PRIMARY KEY 约束。
MERGE 的改进情况如何?
PostgreSQL 18 为 MERGE 语句扩展了 RETURNING 子句支持,完善了 PG 15 引入的功能集。