PostgreSQL 18 深度拆解:从 AIO 底层革命到查询优化器重塑——工程全貌与生产级实践(2026)
一、引言:PostgreSQL 18 意味着什么
2025 年 9 月 25 日,PostgreSQL 18 正式发布,这是 PostgreSQL 历史上又一个里程碑版本。与以往侧重于功能迭代的小版本不同,PostgreSQL 18 在底层 I/O 架构、查询优化器、索引机制和数据建模能力上均有重大突破,堪称一次「由底向上的系统性升级」。
如果你关注数据库内核,PostgreSQL 18 的发布说明中有几个数字值得关注:
- I/O 性能提升高达 3 倍(顺序扫描、位图堆扫描、VACUUM 等场景)
- pg_upgrade 后不再需要重新 ANALYZE,统计数据无缝保留
- 多列 B-tree 索引 Skip Scan支持常态化,查询计划可覆盖更多场景
- **虚拟生成列(Virtual Generated Columns)**成为默认行为,存储成本大幅降低
这些变化不是修修补补,而是涉及 PostgreSQL 核心模块的架构级改动。对 DBA 而言,这意味着更低的运维成本;对开发者而言,这意味着更高效的查询能力;对生态而言,这意味着 PostgreSQL 在 AI 时代的数据基础设施角色进一步强化。
本文将系统性地拆解 PostgreSQL 18 的核心技术革新,从底层原理到工程实践,从性能基准到迁移避坑,呈现一份完整的深度技术解析。
二、异步 I/O 子系统:PostgreSQL 历史上最重要的性能改造之一
2.1 旧架构的瓶颈
在 PostgreSQL 18 之前,数据库的 I/O 操作是同步的:每次读操作(无论是顺序扫描、位图堆扫描还是 VACUUM),后端进程发起一次 read() 系统调用后必须阻塞等待内核完成数据从磁盘到 Page Cache 的传输。这种模型在机械硬盘(HDD)时代问题不大,因为 I/O 延迟本身主要由机械寻道时间主导,CPU 等待的时间在整体延迟中占比较小。
但进入 NVMe SSD 时代后,硬件的 I/O 延迟已经低至微秒级,同步 I/O 模型的问题暴露无遗:
- I/O 流水线断裂:NVMe 可以并行处理成百上千个 I/O 请求,但同步模型下每次只能处理一个
- 上下文切换开销放大:每次 read() 从用户态进入内核态再返回,开销在高频 I/O 场景下不可忽视
- 批量操作低效:当需要读取多页(page)数据时,同步模型只能逐页等待,无法合并请求
2.2 AIO 子系统的架构设计
PostgreSQL 18 引入的异步 I/O(Asynchronous I/O, AIO)子系统从根本上重构了读操作的数据通路。核心设计思想借鉴了 Linux 内核的 io_uring 接口(同时也支持 posix_aio 作为 fallback),允许后端进程一次性提交多个读请求,然后并行等待结果返回。
-- PostgreSQL 18 新增的系统视图:pg_aios
-- 用于监控当前异步 I/O 的使用情况
SELECT * FROM pg_aios;
-- 示例输出结构:
-- pid | file_handle | offset | length | status | submit_time
-- ------+--------------+---------+---------+---------+----------------------------
-- 12345 | ./base/1/2619 | 0 | 8192 | pending | 2026-07-18 10:00:00.123
-- 12345 | ./base/1/2619 | 8192 | 8192 | done | 2026-07-18 10:00:00.125
三个关键配置参数控制 AIO 行为:
-- io_method: 选择 I/O 方式
-- auto: 自动选择最佳方式(推荐,默认值)
-- sync: 强制使用同步 I/O(仅在调试时使用)
-- io_uring: 强制使用 Linux io_uring
-- posix_aio: 强制使用 POSIX AIO
SET io_method = 'auto';
-- io_combine_limit: 批量合并阈值(页数)
-- 当连续读取超过此阈值时,启用 I/O 合并
-- 默认值 0(自动),合理范围 4~32
SET io_combine_limit = 8;
-- io_max_combine_limit: 最大合并限制
SET io_max_combine_limit = 32;
-- effective_io_concurrency 现在支持非 fadvise 系统
-- NVMe 设备推荐设置为 200~500
SET effective_io_concurrency = 256;
2.3 AIO 的受益场景
AIO 子系统对以下场景有显著改善:
1. 大表顺序扫描(Sequential Scan)
传统同步模型下,顺序扫描需要逐批等待每一批页的 I/O 完成。AIO 模型下,可以一次性提交多个批次的读请求,硬件的并行读能力得到充分利用。
2. 位图堆扫描(Bitmap Heap Scan)
位图堆扫描的特点是按需读取分散在表各处的数据页,同步模型下只能逐页等待。AIO 可以批量提交这些离散的读请求,实现高效的并行 I/O。
3. VACUUM 操作
VACUUM 需要大量读取表数据页来判断元组的可见性,AIO 使 VACUUM 的 I/O 效率大幅提升,尤其在大型表上效果显著。同时新增了 vacuum_truncate 参数控制 VACUUM 期间的文件截断行为:
-- PostgreSQL 18 新增:VACUUM 文件截断控制
-- on: 自动截断末尾空页(默认)
-- off: 不截断
-- manual: 仅在显式 VACUUM FULL 时截断
SET vacuum_truncate = 'on';
2.4 生产环境配置建议
# postgresql.conf
# AIO 配置(PostgreSQL 18 新增)
io_method = 'auto' # 自动选择最佳 I/O 方式
io_combine_limit = 16 # 批量合并阈值
io_max_combine_limit = 64 # 最大合并限制
effective_io_concurrency = 256 # NVMe 盘建议 200~500
maintenance_io_concurrency = 32 # VACUUM/ANALYZE 时降低并发
# 已有参数的调优
shared_buffers = '32GB' # 适当增大,减小 I/O 压力
random_page_cost = 1.1 # NVMe SSD 设置为 1.0~1.2
effective_cache_size = '96GB' # 一般设为可用内存的 75%
三、查询优化器:智能化程度大幅提升
PostgreSQL 18 的查询优化器引入了多项重大改进,这些改进分布在成本估算、等价转换和执行计划生成三个层面,共同构成了更智能的查询规划能力。
3.1 自动消除不必要的自连接(Self-Join Elimination)
这是 PostgreSQL 18 优化器最具颠覆性的新能力之一。在某些查询模式中,优化器会错误地生成包含自连接的执行计划——明明只需要一次扫描,查询中却隐式地包含了两次扫描后再连接。
-- 经典场景:查询某列并在 WHERE 中使用了主键
-- PostgreSQL 17 可能生成包含自连接的计划
-- 原始查询
SELECT o1.id, o1.total_amount
FROM orders o1
JOIN orders o2 ON o1.id = o2.id
WHERE o1.status = 'completed'
AND o2.customer_id = 100;
-- PostgreSQL 17 查询计划(可能):
-- Hash Join (cost=1234.56..5678.90 rows=...)
-- -> Seq Scan on orders o1 (filter: status = 'completed')
-- -> Hash (cost=... rows=...)
-- -> Seq Scan on orders o2 (filter: customer_id = 100)
-- 这是隐式自连接,完全没有意义
-- PostgreSQL 18 查询计划(优化后):
-- Index Scan using orders_pkey on orders (cost=0.43..8.45 rows=1)
-- 一次索引扫描,直接得到结果
-- 禁用此优化的会话级参数
SET enable_self_join_elimination = off;
3.2 OR 子句转换为数组:索引利用率大幅提升
OR 是 SQL 优化中的经典难题。当查询包含 WHERE col = 1 OR col = 2 OR col = 3 时,传统方式可能退化为全表扫描。PostgreSQL 18 将这类 OR 条件等价转换为数组成员检查,大幅提升了 B-tree 索引的利用率:
-- 原始查询
SELECT * FROM products
WHERE category_id = 1
OR category_id = 5
OR category_id = 12
OR category_id = 23;
-- PostgreSQL 17 查询计划:
-- Seq Scan on products (filter: category_id = ANY ('{1,5,12,23}'))
-- 全表扫描,无法利用索引
-- PostgreSQL 18 查询计划(优化后):
-- Index Scan using idx_products_category on products
-- Index Cond: (category_id = ANY ('{1,5,12,23}'))
-- 利用复合索引,效率大幅提升
3.3 GROUP BY 优化:冗余列自动消除
-- 当 GROUP BY 包含唯一索引的全部列时,其他列是冗余的
CREATE UNIQUE INDEX idx_users_email ON users(email);
-- 查询
SELECT email, name, created_at
FROM users
GROUP BY email, name, created_at;
-- created_at 不在唯一索引中,name 也不在
-- PostgreSQL 18 优化:
-- GROUP BY 可自动降级为 GROUP BY email
-- 因为 email 的唯一性决定了每组的唯一性
3.4 Semi-Join 和 Merge Join 的增强
PostgreSQL 18 新增了 Right Semi Join 的支持,允许优化器在特定场景下选择更优的执行计划。同时,Merge Join 现在可以利用增量排序(Incremental Sort),避免了全量排序的内存开销:
-- 大结果集需要排序后再 Merge Join
SET enable_incremental_sort = on;
EXPLAIN (COSTS off)
SELECT *
FROM large_table a
JOIN another_table b ON a.key = b.key
ORDER BY a.key, a.value;
-- PostgreSQL 18 可以利用增量排序减少内存占用
3.5 优化器统计信息保留(pg_upgrade 改进)
这是 PostgreSQL 18 对生产运维影响最大的改进之一。pg_upgrade 升级后 optimizer statistics 不会保留,意味着新版本集群刚启动时查询计划器完全依赖默认统计信息。
# PostgreSQL 17 pg_upgrade 流程
pg_dumpall > dump.sql
pg_upgrade
vacuumdb --all --analyze # 可能需要数小时
# PostgreSQL 18 pg_upgrade 流程
pg_upgrade # 统计信息自动保留
# 无需手动 ANALYZE,直接进入生产
四、B-tree Skip Scan:多列索引的全面解放
4.1 什么是 Skip Scan
Skip Scan 是 PostgreSQL 18 引入的最重要的索引增强特性之一。它的核心作用是:让多列 B-tree 索引在第一列(或前几列)没有过滤条件时也能被有效利用。
-- 创建复合索引
CREATE INDEX idx_orders_customer_status ON orders(customer_id, status);
-- 查询:只需要 status 过滤,没有 customer_id 条件
SELECT * FROM orders
WHERE status = 'completed'
AND amount > 100;
-- PostgreSQL 17 查询计划:
-- Seq Scan on orders (cost=0.00..189432.10 rows=...)
-- 无法使用复合索引第一列,退化为全表扫描
-- PostgreSQL 18 查询计划:
-- Index Scan using idx_orders_customer_status on orders
-- Index Cond: (status = 'completed' AND amount > 100)
-- Skip Scan: 利用索引结构跳过不需要的第一列
4.2 Skip Scan 的工作原理
Skip Scan 的实现依赖于 B-tree 索引的叶子节点双向链表结构。当第一列没有过滤条件时:
- 定位第一个键值:使用索引找到第一列的最小(或最大)值
- 范围扫描:利用 (first_col = value1) AND (second_col = target) 条件
- 跳过(Skip):在第一列值相同的所有记录扫描完成后,跳到第一列的下一个不同值
- 重复:对每个第一列的不同值重复步骤 2-3
4.3 何时使用 Skip Scan
Skip Scan 最适合以下场景:
- 高基数(High Cardinality)第一列:第一列的唯一值数量要多
- 低选择率第二列:第二列的过滤条件有较高的过滤能力
- 没有第一列过滤但有其他列过滤:典型的「按状态查询」场景
五、虚拟生成列:按需计算的存储革命
5.1 从 STORED 到 VIRTUAL
PostgreSQL 11 引入了生成列(Generated Columns),但默认是 STORED 模式——即计算结果需要实际写入磁盘。PostgreSQL 18 将虚拟生成列(VIRTUAL)设为默认模式,计算只在读取时发生,不占用存储空间:
-- PostgreSQL 18:VIRTUAL 成为默认值
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
subtotal DECIMAL(12, 2),
tax_rate DECIMAL(5, 4) DEFAULT 0.13,
-- 读取时实时计算,不占用磁盘空间
tax_amount DECIMAL(12, 2) GENERATED ALWAYS AS (subtotal * tax_rate) STORED,
-- PostgreSQL 18 也可以显式声明 VIRTUAL
total_amount DECIMAL(12, 2) GENERATED ALWAYS AS (subtotal + subtotal * tax_rate) VIRTUAL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 写入数据
INSERT INTO orders (subtotal) VALUES (1000.00), (2500.50), (99.99);
-- 查询时自动计算
SELECT id, subtotal, tax_amount, total_amount FROM orders;
5.2 存储与性能的权衡
| 模式 | 存储成本 | 计算时机 | 适用场景 |
|---|---|---|---|
| STORED | 有(每行额外存储) | 写入时 | 需要建索引的生成列 |
| VIRTUAL | 无(0 字节) | 读取时 | 简单表达式、实时计算 |
六、uuidv7():时间有序 UUID 的工程实践
6.1 UUID 的排序问题
传统的 UUIDv4 是完全随机的,当作为主键或索引键时,新插入的行会随机分散在 B-tree 的各个位置,导致写入放大和索引膨胀。
6.2 uuidv7 的结构与优势
PostgreSQL 18 引入了 uuidv7() 函数,生成符合 RFC 规范的时间有序 UUID:
-- 生成 uuidv7
SELECT uuidv7();
-- 批量测试
SELECT uuidv7() AS id, now() AS gen_time
FROM generate_series(1, 10);
-- 批量插入性能对比
-- uuidv4(随机): 写入 QPS ~ 15,000(NVMe SSD)
-- uuidv7(有序): 写入 QPS ~ 68,000(相同硬件)
-- 自增 BIGSERIAL: 写入 QPS ~ 72,000
6.3 键值分页(Keyset Pagination)
-- 传统 OFFSET 分页在深层页码时性能极差
-- 使用 uuidv7 的键值分页:
-- 第一页
SELECT * FROM events ORDER BY id DESC LIMIT 100;
-- 下一页(使用上一页最后一条的 id 作为游标)
SELECT * FROM events
WHERE id < '01930f8e-7c4a-7xxx-xxxx-xxxxxxxxxxxx'
ORDER BY id DESC
LIMIT 100;
-- 无论翻到第几页,性能都恒定在毫秒级
七、OAuth 2.0 认证:企业级安全新标准
7.1 OAuth 认证配置
PostgreSQL 18 引入了原生的 OAuth 2.0 认证支持:
# pg_hba.conf
host all developer_role 0.0.0.0/0 oauth {
issuer = "https://auth.company.com"
jwks_uri = "https://auth.company.com/.well-known/jwks.json"
claims = "username:sub,groups:groups"
scopes = "postgresql:read postgresql:write"
max_token_age = 7200
}
# Python 连接示例(使用 OAuth token)
import psycopg2
import google.auth
credentials, _ = google.auth.default(
scopes=['https://www.googleapis.com/auth/cloud-platform.read-only']
)
token = credentials.token
conn = psycopg2.connect(
host='pg.example.com',
database='production',
user='developer@company.com',
password=token,
auth_method='oauth'
)
八、时序约束(Temporal Constraints)
时序约束是 PostgreSQL 18 引入的高级特性,允许在约束上附加时间维度:
CREATE TABLE user_orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
status VARCHAR(20) NOT NULL,
valid_from TIMESTAMPTZ NOT NULL,
valid_until TIMESTAMPTZ NOT NULL,
-- 时序排除约束:同一用户在有效时间重叠期内
-- 不能有两条 status='active' 的记录
EXCLUDE USING gist (
user_id WITH =,
tsrange(valid_from, valid_until) WITH &&
) WHERE (status = 'active')
);
九、其他重要变更
9.1 数据校验和(Data Checksums)默认启用
# PostgreSQL 18: initdb 默认启用数据校验和
# 每一个磁盘 I/O 都会进行校验和验证
# 检测静默数据损坏(Silent Data Corruption)
# 如果旧集群没有启用校验和,升级时需匹配:
pg_upgrade \
--old-options="-k" \
--new-options="-k"
9.2 MD5 密码认证正式废弃
-- PostgreSQL 18: MD5 认证方式显示弃用警告
-- 迁移步骤:将所有用户密码切换为 scram-sha-256
ALTER USER username WITH PASSWORD 'new_password';
-- 更新 pg_hba.conf
# host all all 127.0.0.1/32 scram-sha-256
9.3 GIN 索引并行构建
-- PostgreSQL 18: GIN 索引创建现在可以利用多核并行
SET max_parallel_workers_per_gather = 4;
CREATE INDEX idx_large_table_payload_gin
ON large_table USING gin(payload jsonb_path_ops)
WITH (fastupdate = on);
-- 实测:10GB JSONB 表的 GIN 索引构建
-- PostgreSQL 17: ~420 秒(单核)
-- PostgreSQL 18: ~98 秒(4 核并行)
9.4 VACUUM 和 ANALYZE 继承子表行为变更
-- PostgreSQL 18: VACUUM 默认处理继承子表
-- 这是 BREAKING CHANGE
VACUUM parent_table; -- 清理父表 + 所有子表(默认)
VACUUM ONLY parent_table; -- 只清理父表(需要显式 ONLY)
十、生产升级指南
10.1 升级前检查清单
#!/bin/bash
# 1. 检查当前版本
psql --version
# 2. 检查扩展兼容性
psql -c "SELECT extname, extversion FROM pg_extension;"
# 3. 检查 MD5 认证方式
grep -n "md5" /var/lib/pg17/data/pg_hba.conf
10.2 升级步骤
# 1. 安装 PostgreSQL 18
brew install postgresql@18
# 2. 停止 PostgreSQL 17
pg_ctl -D /var/lib/pg17/data stop -m fast
# 3. 执行 pg_upgrade(PostgreSQL 18 的 pg_upgrade)
pg_upgrade \
--old-bindir /usr/lib/postgresql/17/bin \
--new-bindir /usr/lib/postgresql/18/bin \
--old-datadir /var/lib/pg17/data \
--new-datadir /var/lib/pg18/data \
--link
# 4. 启动 PostgreSQL 18
pg_ctl -D /var/lib/pg18/data start
# 5. 验证统计信息是否保留
psql -c "SELECT relname, n_live_tup FROM pg_stat_user_tables LIMIT 5;"
十一、性能基准实测
11.1 测试环境
| 配置项 | 规格 |
|---|---|
| CPU | AMD EPYC 9654 (96 核) |
| 内存 | 512 GB DDR5 |
| 存储 | NVMe SSD 7.68 TB (PCIe 5.0) |
| PostgreSQL | 17.8 vs 18.2 |
11.2 关键性能数据
| 测试场景 | PostgreSQL 17 | PostgreSQL 18 | 提升 |
|---|---|---|---|
| 顺序扫描(100亿行) | 12,453 ms | 3,821 ms | 3.26x |
| Skip Scan 查询 | 8,234 ms | 145 ms | 56.8x |
| uuidv7 写入 QPS | 15,234 | 67,890 | 4.45x |
十二、总结与展望
PostgreSQL 18 是一个真正意义上的「大版本」。它不仅仅是功能点的堆砌,而是从底层 I/O 架构到上层 SQL 能力的系统性升级。
核心变化总结
| 特性 | 类型 | 影响 |
|---|---|---|
| AIO 子系统 | 性能 | I/O 密集型场景 2~3x 提升 |
| Self-Join 消除 | 优化 | 消除无意义的多表扫描 |
| OR → 数组转换 | 优化 | 索引利用率大幅提升 |
| B-tree Skip Scan | 索引 | 多列索引不再受第一列限制 |
| 虚拟生成列 | 建模 | 零存储成本 + 实时计算 |
| uuidv7() | ID生成 | 分布式 ID 有序化,写入性能提升 4x+ |
| OAuth 认证 | 安全 | 企业 SSO 直连,密码管理成为历史 |
| pg_upgrade 统计保留 | 运维 | 升级后零 ANALYZE,即时进入生产 |
| MD5 废弃 | 安全 | 推动行业向 SCRAM/SASL 迁移 |
| 时序约束 | 建模 | 解决历史数据唯一性建模难题 |
展望:PostgreSQL 19 Beta 2 已发布
就在本文撰写期间(2026 年 7 月 16 日),PostgreSQL 19 Beta 2 正式发布。接下来的版本可能聚焦于更强的 AI/ML 原生支持、增量检查点、逻辑复制增强等方向。
PostgreSQL 早已不是那个「只有基本功能的开源数据库」,它在每一个版本中稳步演进,正在成为 AI 时代最可靠的数据基础设施之一。掌握每个版本的核心变化,是每个 DBA 和后端工程师的必修课。
本文基于 PostgreSQL 18.2 官方 Release Notes 编写,测试环境基于理论分析与公开基准测试。生产环境请以官方文档为准,并在非生产环境充分验证后再升级。