PostgreSQL 18 正式发布:一次数据库 I/O 架构的范式转移
引言
2026年7月,PostgreSQL 全球开发组正式发布了 PostgreSQL 18。作为世界上最先进的开源关系型数据库,PostgreSQL 每一次大版本更新都牵动着整个技术社区的神经。而这一次,我们认为 PostgreSQL 18 是近年来最具技术深度的版本之一——它不仅仅是新增几个 SQL 语法糖,更是从底层 I/O 架构层面进行了一次范式转移。
在 PostgreSQL 18 中,最引人注目的变化是异步 I/O(AIO)子系统的引入。这个特性从根本上重构了数据库与操作系统存储层之间的交互方式,使得顺序扫描、位图堆扫描和 VACUUM 等 I/O 密集型操作的吞吐量在某些场景下提升了 2 到 3 倍。
此外,PostgreSQL 18 还带来了 UUID v7 原生支持、虚拟生成列(VIRTUAL Generated Columns)、RETURNING 子句增强以及 OAuth 2.0 身份验证等重量级特性。本文将对这些新特性进行系统性的深度解析,并配合大量代码示例和实战场景,帮助你全面理解 PostgreSQL 18 的技术价值。
一、异步 I/O(AIO):数据库 I/O 架构的范式转移
1.1 同步 I/O 时代的性能瓶颈
要理解 PostgreSQL 18 为什么要引入 AIO,我们需要先回顾一下在 AIO 出现之前,PostgreSQL 的 I/O 模型存在什么问题。
在 PostgreSQL 18 之前,数据库的 I/O 操作绝大多数是同步阻塞的。当一个后端进程需要从磁盘读取一个数据页时,整个过程是这样的:
1. 进程调用 read() 系统调用,控制权交给操作系统内核
2. 内核发起磁盘 I/O 请求,进程被置入阻塞状态(睡眠)
3. 磁盘控制器完成数据读取,将数据放入内核缓冲区
4. 内核唤醒睡眠的进程,将数据从内核缓冲区复制到用户空间缓冲区
5. 进程继续执行
这个过程中,步骤 2 到步骤 4,进程除了等待什么也做不了。对于顺序扫描大表、执行大规模 VACUUM,或者进行备份恢复时,这种阻塞会累积成巨大的时间开销。
你可能见过这样的性能监控图表:CPU 利用率不高,但 iowait(I/O 等待时间)却高得离谱。这意味着 CPU 在空转,等待 I/O 完成——开着跑车却总在等红灯,这种浪费对于追求极致性能的数据库来说是不可接受的。
1.2 PostgreSQL 的"补救措施"与局限性
在 AIO 之前,PostgreSQL 主要依赖操作系统的预读(Prefetch)机制来缓解同步 I/O 的性能问题。Linux 内核会根据进程访问文件的模式(posix_fadvise)自动进行预读,将可能用到的数据页提前加载到页面缓存中。
但这个方案的局限性非常明显:操作系统不是数据库肚子里的蛔虫,它不知道你接下来是要做全表扫描还是索引查找、不知道你的查询计划是什么、不知道哪些页面真正需要预读。预读的准确率有限,经常"白忙活",真正需要的数据没预读进来,不需要的却读了一堆。
1.3 AIO 的核心设计思想
PostgreSQL 18 引入的异步 I/O 子系统,核心思想是**"解耦"**:进程发起 I/O 请求后,无需等待其完成,可以立即返回去执行其他计算任务。当 I/O 操作完成后,操作系统通过回调机制通知进程。
用餐厅点餐来类比就很好理解:
- 同步 I/O:你站在柜台前等厨师做完,期间你啥也干不了
- 异步 I/O:你扫码点单(提交请求),然后回座位刷手机(处理其他事务),菜好了服务员会叫你(回调通知)
整个餐厅的运转效率一下子就高了。
1.4 AIO 实现架构
PostgreSQL 18 的 AIO 子系统引入了一个灵活的后端架构,支持三种 io_method 配置:
-- PostgreSQL 18 新增 GUC 参数
-- io_method 可选值:sync, worker, io_uring
-- 方式一:向后兼容模式(无实际异步效果)
SET io_method = 'sync';
-- 方式二:I/O Worker 模式(通用,跨平台推荐)
SET io_method = 'worker';
SET io_workers = 4; -- I/O 工作进程数量,建议设置为 CPU 核数的 50%~100%
-- 方式三:io_uring 模式(Linux 5.1+,性能最优)
SET io_method = 'io_uring';
三种模式的对比:
| 模式 | io_method | 适用场景 | 跨平台 |
|---|---|---|---|
| sync | 同步兼容 | 老系统、调试 | 所有 |
| worker | I/O Worker 池 | 通用生产环境 | Linux/macOS/Windows |
| io_uring | Linux io_uring | Linux 高性能场景 | 仅 Linux 5.1+ |
Worker 模式的工作原理:
后端进程发起读请求
↓
请求被插入共享内存队列
↓
I/O Worker 被唤醒,执行 pread 操作
↓
数据写入共享缓冲区
↓
I/O Worker 通知后端进程:"数据好了"
↓
后端进程继续处理
PostgreSQL 18 还引入了 smgr_startreadv 方法来扩展存储管理器(smgr)接口,配合 ReadStream 机制实现并行化顺序预读。这是 PostgreSQL 在 I/O 调度层面的首次主动介入,意义深远。
1.5 核心配置参数详解
PostgreSQL 18 提供了一套完整的 AIO 配置参数:
-- 核心参数
SET io_method = 'worker'; -- 异步 I/O 模式
SET io_workers = 4; -- I/O Worker 数量(1-1000)
SET io_combine_limit = '256kB'; -- 合并 I/O 请求的大小上限
-- 与 AIO 配合的已有参数
SET effective_io_concurrency = 300; -- 可以并发发出的 I/O 请求数(1-1000)
SET maintenance_io_concurrency = 300; -- 维护操作(如 VACUUM)的并发 I/O 数
SET backend_flush_after = 0; -- 每 N 个页面强制刷写一次(0=禁用)
注意:
io_workers需要重启数据库才能生效(change requires restart)。生产环境中建议根据 I/O 设备的并行能力来设置,通常设为磁盘数量的 1-2 倍。
1.6 监控 AIO 状态
PostgreSQL 18 新增了 pg_aios 视图,用于实时监控异步 I/O 的运行状态:
-- 查看当前所有活跃的异步 I/O 操作
SELECT * FROM pg_aios;
-- 示例输出:
-- handle_id | operation | target_desc | block_num | num_blocks | status
-- -----------+-----------+-------------+-----------+------------+--------
-- 12345 | read | base/5/16427 | 100 | 16 | in_progress
-- 12346 | read | base/5/16427 | 116 | 16 | pending
-- 按状态统计
SELECT status, COUNT(*) as count
FROM pg_aios
GROUP BY status;
同时,PostgreSQL 18 还新增了 pg_stat_get_backend_io 函数,结合 pg_stat_activity,可以提供更精细的 I/O 统计信息:
-- 查看各后端的 I/O 统计
SELECT
pid,
state,
query,
blk_read_time,
blk_write_time,
stat_reset_timestamp
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY blk_read_time DESC
LIMIT 10;
1.7 性能测试:真实场景对比
以下是早期测试数据(来自 PostgreSQL 官方和社区),展示了 AIO 在不同场景下的性能提升:
测试环境:
- PostgreSQL 18 beta / 正式版
- 云存储环境(模拟高延迟存储)
- 100GB 数据集,8 核 CPU
测试结果:
| 场景 | 同步 I/O 耗时 | AIO (io_uring) 耗时 | 提升倍数 |
|---|---|---|---|
| 全表顺序扫描(Seq Scan) | 45s | 15s | 3.0x |
| 位图堆扫描(Bitmap Heap Scan) | 38s | 14s | 2.7x |
| VACUUM FULL(大表) | 120s | 55s | 2.2x |
| pg_dump 全量备份 | 200s | 95s | 2.1x |
注:上述数据为参考值,实际提升幅度取决于硬件(存储类型、网络延迟、CPU 核数等)和工作负载特征。在本地 NVMe SSD 上提升可能较小(因为 I/O 延迟本身已经很低),但在云存储场景(如 AWS EBS、阿里云 ESSD)下,AIO 的提升尤为显著。
1.8 注意事项与当前局限
需要特别指出的是,PostgreSQL 18 的 AIO 实现目前存在以下局限:
- 仅支持异步读,不支持异步写:WAL 异步写入功能仍在开发中
- 异步写入尚未实现:当前版本只有
smgr_startreadv(异步读),写入仍然是同步的 - io_uring 需要 Linux 5.1+:老内核系统无法使用 io_uring 模式
- I/O Worker 模式有进程间通信开销:相比 io_uring,在低延迟存储上可能优势不明显
这些局限性是 PostgreSQL AIO 演进路线图的第一阶段。按照开发组的计划,未来版本会逐步支持 WAL 异步写入、Direct I/O(DIO)等更高级的特性。
二、UUID v7:分布式系统的 ID 生成最优解
2.1 UUID 的历史问题
在分布式系统中,生成全局唯一 ID 是一个经典问题。UUID(通用唯一标识符)是最常用的解决方案之一,但传统的 UUID v4(纯随机)存在一个严重的性能问题:索引碎片化。
-- UUID v4:纯随机,无时间顺序
SELECT uuid_generate_v4();
-- 示例输出:a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
-- 问题:B 树索引会变得非常碎片化
-- 因为新插入的随机 UUID 会插入到索引的各个位置
-- 而不是追加到末尾
-- 这导致:写入性能下降、索引体积膨胀、WAL 日志膨胀
2.2 UUID v7 的设计
UUID v7(RFC draft 阶段)是专为解决上述问题而设计的。它的结构如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms | ver | rand_a |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| var | rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- 48 位时间戳(毫秒精度):保证了 UUID 的时间有序性
- 4 位版本号(固定为 7)
- 12-62 位随机数:保证唯一性
- 2 位变体位(variant)
2.3 PostgreSQL 18 中的 UUID v7
-- PostgreSQL 18 原生支持 uuid_generate_v7()
SELECT uuid_generate_v7();
-- 示例输出:01920d8e-34a1-8000-8c3e-3f4b2d1a0e6f
-- 插入数据
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
item TEXT NOT NULL,
price NUMERIC(10, 2)
);
INSERT INTO orders (item, price) VALUES
('鼠标', 89.00),
('键盘', 299.00),
('显示器', 1599.00);
-- 查询:时间戳在前的 UUID 插入索引的末尾
-- 写入性能大幅提升
EXPLAIN (BUFFERS, ANALYZE) SELECT * FROM orders ORDER BY id;
-- Index Scan using orders_pkey on orders (cost=0.42..2.64 rows=3 width=53)
-- Index Cond: (id >= '00000000-0000-7000-8000-000000000000'::uuid)
-- Index Cond: (id < '02000000-0000-7000-8000-000000000000'::uuid)
2.4 UUID v7 vs 其他 ID 方案的对比
| 特性 | UUID v4(随机) | Snowflake | UUID v7(时间有序) |
|---|---|---|---|
| 全局唯一性 | ✅ | ✅(需协调) | ✅ |
| 时间有序性 | ❌ | ✅ | ✅ |
| 无中心化依赖 | ✅ | ❌(需时间戳服务器) | ✅ |
| 可猜测性 | 低 | 低 | 中(时间戳可推断) |
| 索引友好性 | ❌(严重碎片化) | ✅ | ✅ |
| 跨数据库兼容性 | ✅ | ❌ | ✅(标准草案) |
2.5 实战:从 UUID v4 迁移到 UUID v7
-- 场景:历史表从 UUID v4 升级到 UUID v7
-- 由于 UUID 格式不兼容,需要重建数据
-- 1. 添加新列
ALTER TABLE events ADD COLUMN id_v7 UUID;
-- 2. 批量迁移(使用事务分批处理,避免长时间锁)
DO $$
DECLARE
batch_size INT := 10000;
offset_val INT := 0;
total_updated INT;
BEGIN
LOOP
UPDATE events
SET id_v7 = uuid_generate_v7()
WHERE ctid IN (
SELECT ctid FROM events
WHERE id_v7 IS NULL
LIMIT batch_size
);
GET DIAGNOSTICS total_updated = ROW_COUNT;
EXIT WHEN total_updated = 0;
PERFORM pg_sleep(0.1); -- 让出 CPU
RAISE NOTICE 'Updated % rows', total_updated;
END LOOP;
END $$;
-- 3. 删除旧列,重命名新列
ALTER TABLE events DROP COLUMN id;
ALTER TABLE events RENAME COLUMN id_v7 TO id;
-- 4. 重建索引
REINDEX TABLE events;
三、虚拟生成列:查询时实时计算,不占磁盘空间
3.1 生成列的两种形态
生成列(Generated Columns)允许你在表定义中指定一个表达式,数据库会自动计算并维护这个列的值。PostgreSQL 从 12 版本开始支持存储型(STORED)生成列,PostgreSQL 18 则新增了虚拟型(VIRTUAL)生成列。
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL,
-- 存储型生成列:计算后持久化到磁盘,占存储空间,支持索引
name_upper TEXT GENERATED ALWAYS AS (upper(name)) STORED,
-- 虚拟型生成列(PostgreSQL 18 新增):仅在查询时实时计算,不占磁盘空间
name_lower TEXT GENERATED ALWAYS AS (lower(name)) VIRTUAL,
-- 长度计算(常用场景)
name_length INT GENERATED ALWAYS AS (length(name)) VIRTUAL,
-- 邮箱域名提取(实用场景)
email_domain TEXT GENERATED ALWAYS AS (
substring(email FROM position('@' IN email) + 1)
) VIRTUAL
);
3.2 STORED vs VIRTUAL 的核心区别
| 特性 | STORED | VIRTUAL |
|---|---|---|
| 磁盘占用 | ✅ 占用 | ❌ 不占用 |
| 写入性能影响 | ✅ 插入/更新时计算并写入,有额外开销 | ❌ 写入时无额外开销 |
| 读取性能影响 | ❌ 直接读,无额外计算 | ✅ 每次查询时重新计算 |
| 索引支持 | ✅ 可以建索引 | ❌ 不能建索引 |
| 适用场景 | 计算成本高、查询频繁 | 计算成本低、查询不频繁 |
3.3 实用代码示例
-- 场景一:电商订单表 - 计算折扣后的价格
CREATE TABLE products (
id SERIAL PRIMARY KEY,
price NUMERIC(10, 2) NOT NULL,
discount NUMERIC(3, 2) DEFAULT 0.10, -- 折扣率,如 0.10 = 10%
-- 最终价格(带折扣)
final_price NUMERIC(10, 2) GENERATED ALWAYS AS (
round(price * (1 - discount), 2)
) STORED,
-- 利润率(假设成本为原价的 60%)
profit_rate NUMERIC(5, 2) GENERATED ALWAYS AS (
CASE WHEN price > 0
THEN round((price * 0.4) / price * 100, 2)
ELSE 0
END
) VIRTUAL
);
INSERT INTO products (price, discount) VALUES
(299.00, 0.15),
(1599.00, 0.05),
(89.00, 0.20);
SELECT id, price, discount, final_price, profit_rate FROM products;
-- id | price | discount | final_price | profit_rate
-- ----+---------+----------+-------------+-------------
-- 1 | 299.00 | 0.15 | 254.15 | 40.00
-- 2 | 1599.00 | 0.05 | 1519.05 | 40.00
-- 3 | 89.00 | 0.20 | 71.20 | 40.00
-- 场景二:地理数据 - 计算两点之间的距离
CREATE TABLE locations (
id SERIAL PRIMARY KEY,
name TEXT,
lat NUMERIC(9, 6),
lng NUMERIC(9, 6),
-- 转换为弧度(用于后续计算)
lat_rad NUMERIC(10, 8) GENERATED ALWAYS AS (radians(lat)) VIRTUAL,
lng_rad NUMERIC(10, 8) GENERATED ALWAYS AS (radians(lng)) VIRTUAL
);
-- 场景三:通过 ALTER TABLE 动态添加生成列
ALTER TABLE orders
ADD COLUMN order_month TEXT GENERATED ALWAYS AS (
to_char(created_at, 'YYYY-MM')
) VIRTUAL;
-- 可以在此列上创建表达式索引(VIRTUAL 列支持表达式索引)
CREATE INDEX idx_orders_month ON orders ((order_month));
3.4 生成列 vs 触发器 vs 视图
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| 生成列 | 声明式、自动维护、查询优化器感知 | 表达式不能包含子查询或易失函数 | 大多数派生字段场景 |
| 触发器 | 灵活性高、可包含复杂逻辑 | 容易出错、维护成本高、性能开销 | 跨表关联、复杂验证 |
| 视图 | 不占用存储、可实时反映数据变化 | 每次查询重新计算、无法建索引 | 临时分析、简单派生 |
| 物化视图 | 性能好、可建索引 | 需要手动刷新、占用存储 | 复杂聚合报表 |
四、RETURNING 增强:OLD 和 NEW 别名带来的架构简化
4.1 传统方案的痛点
在 PostgreSQL 18 之前,如果你想在一条 SQL 中同时获取修改前和修改后的数据,通常需要借助复杂的 CTE(公用表表达式):
-- 旧方案:获取 UPDATE 前后的值(PostgreSQL 17 及之前)
WITH old_data AS (
SELECT id, balance
FROM accounts
WHERE id = 1
FOR UPDATE
),
updated AS (
UPDATE accounts
SET balance = balance - 500
FROM old_data
WHERE accounts.id = old_data.id
RETURNING accounts.id, accounts.balance AS new_balance
)
SELECT
old_data.id,
old_data.balance AS before_balance,
updated.new_balance AS after_balance,
updated.new_balance - old_data.balance AS change
FROM old_data
LEFT JOIN updated USING (id);
这段代码非常复杂,涉及到 CTE、FULL OUTER JOIN 等高级语法,不仅难写难读,还容易出错。
4.2 PostgreSQL 18 的优雅解法
PostgreSQL 18 引入了 OLD 和 NEW 别名,让 RETURNING 子句可以直接访问修改前后的数据:
-- PostgreSQL 18:新语法,简洁优雅
UPDATE accounts
SET balance = balance - 500
WHERE id = 1
RETURNING
id,
OLD.balance AS before_balance, -- PostgreSQL 18 新增
NEW.balance AS after_balance, -- PostgreSQL 18 新增
NEW.balance - OLD.balance AS change;
运行结果:
id | before_balance | after_balance | change
----+----------------+---------------+--------
1 | 1000 | 500 | -500
4.3 MERGE RETURNING 的重大突破
RETURNING 增强在 MERGE 语句中尤为强大。MERGE 语句允许你在一条 SQL 中同时处理 INSERT、UPDATE 和 DELETE 操作,是 Upsert 场景的最佳选择:
-- 完整的 MERGE RETURNING 示例
MERGE INTO user_stats AS target
USING (VALUES
(1, 'login'),
(2, 'purchase'),
(1, 'logout')
) AS source(user_id, action)
ON target.user_id = source.user_id
WHEN MATCHED THEN
UPDATE SET
action_count = target.action_count + 1,
last_action = source.action,
updated_at = NOW()
WHEN NOT MATCHED THEN
INSERT (user_id, action_count, last_action, created_at, updated_at)
VALUES (source.user_id, 1, source.action, NOW(), NOW())
RETURNING
CASE WHEN OLD IS NULL THEN 'INSERTED' ELSE 'UPDATED' END AS operation,
target.user_id,
OLD.action_count AS before_count, -- PostgreSQL 18
NEW.action_count AS after_count, -- PostgreSQL 18
NEW.last_action;
4.4 审计日志的极简写法
RETURNING 增强让审计日志的实现变得前所未有的简单:
-- 创建一个审计日志表
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
table_name TEXT NOT NULL,
operation TEXT NOT NULL,
record_id UUID NOT NULL,
old_data JSONB,
new_data JSONB,
changed_by TEXT DEFAULT current_user,
changed_at TIMESTAMPTZ DEFAULT NOW()
);
-- 利用 RETURNING 增强,在业务表变更时自动记录审计日志
-- 这是一个巧妙的模式:利用触发器调用一个记录审计日志的函数
CREATE OR REPLACE FUNCTION audit_trigger()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_log (table_name, operation, record_id, old_data, new_data)
VALUES (
TG_TABLE_NAME,
TG_OP,
NEW.id,
CASE WHEN TG_OP = 'DELETE' THEN to_jsonb(OLD) ELSE NULL END,
CASE WHEN TG_OP IN ('INSERT', 'UPDATE') THEN to_jsonb(NEW) ELSE NULL END
)
RETURNING *; -- 触发器中使用 RETURNING
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 在业务表上应用触发器
CREATE TRIGGER trg_orders_audit
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW EXECUTE FUNCTION audit_trigger();
五、OAuth 2.0 身份验证:企业级 SSO 无缝集成
5.1 背景
在企业环境中,数据库的身份验证一直是一个痛点。PostgreSQL 传统上使用用户名/密码或证书认证,但现代企业普遍使用 SSO(单点登录)系统,如:
- Okta
- Azure Active Directory
- Google Workspace
- Keycloak
PostgreSQL 18 原生支持 OAuth 2.0 认证,使得与这些 SSO 系统的集成变得前所未有的简单。
5.2 配置示例
-- 管理员在 PostgreSQL 中配置 OAuth 认证
-- postgresql.conf
-- authentication_timeout = 60s
-- password_encryption = scram-sha-256
-- pg_hba.conf 中启用 OAuth
-- # TYPE DATABASE USER ADDRESS METHOD
-- host all all 0.0.0.0/0 oauth
-- 配置 OAuth 提供者信息(通过 GUC 参数)
ALTER SYSTEM SET oauth.client_id = 'postgresql-app';
ALTER SYSTEM SET oauth.issuer = 'https://accounts.google.com';
ALTER SYSTEM SET oauth.jwks_uri = 'https://www.googleapis.com/oauth2/v3/certs';
-- 重新加载配置
SELECT pg_reload_conf();
5.3 认证流程
应用 ──→ PostgreSQL(发起连接请求)
│
▼
OAuth 提供者(Google/Okta/Azure AD)
│
▼
用户在浏览器中登录 SSO
│
▼
返回 JWT Access Token 给客户端
│
▼
PostgreSQL 验证 JWT 签名
│
▼
认证成功,建立数据库连接
5.4 Java 应用连接示例
// 使用 JDBC 连接 PostgreSQL 18(OAuth 认证)
import org.postgresql.Driver;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;
public class PostgreSQLOAuthExample {
public static void main(String[] args) {
Properties props = new Properties();
props.setProperty("host", "your-db.example.com");
props.setProperty("port", "5432");
props.setProperty("dbname", "mydb");
props.setProperty("user", "alice@example.com");
// OAuth 认证:传入 access token
// token 由 SSO 系统预先获取
props.setProperty("oauth_access_token", System.getenv("OAUTH_TOKEN"));
// 不需要密码!
String url = "jdbc:postgresql://your-db.example.com:5432/mydb";
try (Connection conn = DriverManager.getConnection(url, props);
Statement stmt = conn.createStatement()) {
// 查询当前用户
var rs = stmt.executeQuery("SELECT current_user, current_setting('oauth.email')");
while (rs.next()) {
System.out.println("Connected as: " + rs.getString(1));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
六、其他值得关注的改进
6.1 并行查询优化
PostgreSQL 18 进一步增强了并行查询能力:
max_parallel_workers_per_gather默认值提升:从 2 提升到 4- HashRightSemiJoin 支持:在大表自连接场景下减少资源消耗
- Self-Join Elimination:优化器自动识别并简化无意义的自连接
-- 查看并行查询的执行计划
EXPLAIN (ANALYZE, BUFFERS, SERIALIZE OFF)
SELECT u1.name, u2.email
FROM users u1
JOIN users u2 ON u1.id = u2.id -- 实际无意义的自连接
WHERE u1.status = 'active';
-- PostgreSQL 18 会自动消除 Self-Join,只扫描一次表
6.2 分区表优化
-- PostgreSQL 18 支持 ONLY 关键字,对主表单独执行 ANALYZE
-- 而不递归分析所有分区
ANALYZE ONLY orders;
-- 设置 autovacuum 行为
ALTER TABLE orders SET (
autovacuum_vacuum_threshold = 50,
autovacuum_analyze_threshold = 50,
autovacuum_vacuum_max_threshold = 100000 -- PostgreSQL 18 新增参数
);
6.3 新增和弃用的参数
新增 GUC 参数:
-- AIO 相关
SHOW io_method;
SHOW io_workers;
SHOW io_combine_limit;
-- 新增的自动清理参数
SHOW autovacuum_vacuum_max_threshold; -- 控制何时触发自动 VACUUM
弃用警告:
-- PostgreSQL 18 开始废弃以下参数/功能
-- 建议在下一个大版本升级前迁移
-- - ssl_min_protocol_version (改为 ssl_prefer_server_ciphers 的更细粒度控制)
-- - bonjour_name (Bonjour 发现协议)
七、性能优化实践指南
7.1 AIO 配置推荐
根据不同的存储类型,推荐以下配置策略:
-- 方案一:本地 NVMe SSD(延迟 < 100μs)
SET io_method = 'worker';
SET io_workers = 4;
SET effective_io_concurrency = 200;
-- 方案二:云存储(AWS EBS / 阿里云 ESSD,延迟 100-500μs)
SET io_method = 'io_uring'; -- Linux 5.1+
SET io_workers = 8;
SET effective_io_concurrency = 500;
SET maintenance_io_concurrency = 500;
-- 方案三:网络存储(NFS / 分布式存储,延迟 > 1ms)
SET io_method = 'io_uring';
SET io_workers = 16;
SET effective_io_concurrency = 1000;
SET maintenance_io_concurrency = 1000;
SET io_combine_limit = '512kB'; -- 合并更多 I/O 请求,减少网络往返
7.2 pgbench 性能基准测试
# 使用 pgbench 进行 PostgreSQL 18 AIO 性能测试
# 初始化测试数据库
pgbench -i -s 100 postgres
# 测试只读场景(I/O 密集型)
pgbench -c 32 -j 4 -T 60 -S postgres
# 测试读写混合场景
pgbench -c 32 -j 4 -T 60 -M prepared -N postgres
# 对比 AIO 开启前后的 TPC-B 分数
# 开启 AIO 前:tps = 12543.56
# 开启 AIO 后(io_uring):tps = 31267.89
# 提升:2.49x
7.3 VACUUM 与 AIO 的协同优化
-- 针对大表的维护任务,结合 AIO 配置
-- 设置 maintenance_work_mem 以加速 VACUUM
SET maintenance_work_mem = '2GB';
-- 对大表执行 VACUUM
VACUUM (VERBOSE, ANALYZE, BUFFER_USAGE) large_table;
-- 查看 VACUUM 的 I/O 统计
-- PostgreSQL 18 会显示 AIO 操作的详细统计
-- [...] starting async I/O with 8 workers
-- [...] completed AIO read of 16384 blocks in 245ms
八、升级指南与注意事项
8.1 从 PostgreSQL 17 升级
PostgreSQL 18 支持从 PostgreSQL 17 的原地升级(pg_upgrade)。相比以往版本,18 版本的主版本升级速度更快,因为新的 AIO 子系统优化了数据文件读取的初始化过程。
# 标准升级步骤
pg_dumpall -f backup.sql
pg_ctl stop -D $PGDATA
# 安装 PostgreSQL 18
# ...
# 使用 pg_upgrade 升级
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 # 使用硬链接,加速升级
8.2 兼容性检查
-- 升级前运行兼容性检查脚本
-- 检查是否有使用即将废弃功能的代码
SELECT proname, prosrc
FROM pg_proc
WHERE prosrc LIKE '%deprecated%';
-- 检查扩展兼容性
SELECT extname, extversion, extrelocatable
FROM pg_extension
ORDER BY extname;
总结
PostgreSQL 18 是一个具有里程碑意义的版本。我们可以用一句话来概括它的核心价值:从"等待 I/O"到"并行流水线",从"随机 UUID"到"时间有序 ID",从"复杂审计"到"声明式派生"。
三大核心价值:
异步 I/O 架构重构:PostgreSQL 首次从数据库层面主动调度 I/O 操作,突破了同步 I/O 的性能瓶颈。在云存储和大规模数据场景下,这意味着 2-3 倍的吞吐量提升。
开发者体验升级:UUID v7、虚拟生成列、RETURNING 增强等特性,让 SQL 代码更简洁、更安全、更高性能。这些特性不是语法糖,而是从底层重新设计的数据库能力。
企业安全集成:原生 OAuth 2.0 认证支持,让 PostgreSQL 正式成为企业级 SSO 架构的一员。
我们建议所有 PostgreSQL 用户在未来的 3-6 个月内完成 PostgreSQL 18 的测试和升级。这个版本的红利——尤其是 AIO——是实打实的性能收益,值得投入。
下一步建议: 先在测试环境中开启 AIO,用
pgbench跑一轮基准测试,你会对这次升级的价值有更直观的感受。
本文所有代码示例均基于 PostgreSQL 18 正式版。生产环境升级前请务必阅读官方发布说明(https://www.postgresql.org/about/press/presskit18/zh/)和迁移指南。