跳到主要内容
EngineeringMar 28, 2026

PostgreSQL 18 深度解析:uuidv7、虚拟列与全新 I/O 引擎

OS
Open Soft Team

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 17PG 18提升
顺序扫描(冷缓存)1.2 GB/s3.4 GB/s2.8x
Bitmap heap scan890 MB/s2.6 GB/s2.9x
VACUUM(大表)45 分钟18 分钟2.5x
并行索引构建12 分钟5.5 分钟2.2x
WAL 写入吞吐量1.8 GB/s3.1 GB/s1.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。这是社区多年来一直要求的功能,之前需要 pgcryptouuid-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 PKUUIDv7 PKBIGSERIAL PK
插入速率(行/秒)45,000112,000125,000
索引大小4.2 GB4.2 GB2.1 GB
索引缓存命中率67%94%96%
点查找延迟(p99)2.1 ms0.4 ms0.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

升级前检查清单

  1. 检查扩展兼容性。 在 PG 18 测试实例上运行 SELECT * FROM pg_available_extensions;
  2. 审查 pg_hba.conf。 新的 OAuth 方法是累加的——现有认证配置继续正常工作。
  3. 测试 I/O 性能。 新的异步 I/O 子系统默认启用。
  4. 审计生成列。 如果计划将存储生成列转换为虚拟列,请验证没有索引依赖于它们。
  5. 测试应用程序查询。 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 引入的功能集。