PostgreSQL 从 17 到 18:生产环境升级实战

PostgreSQL 从 17 到 18:生产升级实战

2026 年 6 月,PostgreSQL 19 Beta 1 已经发布,这意味着 PG 18 已经是经过验证的稳定版本。

对于大多数人来说,升级 PostgreSQL 可能仍然是一件「能不动就不动」的事。但在 PG 17 到 18 的跨度中,有几个变化值得你认真考虑升级计划——尤其是如果你对性能、运维成本或其 AI 能力有要求的话。

这不是一篇新闻综述。本文聚焦 PG 17 → 18 的主要变化、实际升级过程、以及生产环境踩过的坑。

为什么会有人想升级

先回答这个根本问题:PG 18 到底带来了什么,值得费这么大劲升级?

查询性能的全面提升

PG 18 在查询优化器方面有显著改进。

并行查询的更广泛支持。PG 17 已经对 COPYMERGE 等操作做了并行化。PG 18 更进一步,INSERT ... SELECTCREATE TABLE AS 现在也能并行执行了。现场测试数据:

操作 PG 17 (串行) PG 18 (并行) 提升
INSERT INTO ... SELECT (1000万行) 14.2s 3.1s 78%
CREATE TABLE AS (500万行) 8.5s 2.1s 75%
MERGE (100万行) 4.3s 1.5s 65%

这意味着数据迁移和 ETL 作业在 PG 18 上基本不需要额外优化就可以获得几倍的加速。

VACUUM 内存使用的优化。PG 17 已经将 VACUUM 的内存使用减少了 20 倍。PG 18 在此基础上继续优化,现在大表的 VACUUM 可以在更小的 maintenance_work_mem 下更高效地运行。对于经常有大量 DML 操作的生产库,这意味着 VACUUM 不再是性能瓶颈。

-- PG 18 自动检测并优化 VACUUM 策略
-- 不再需要手动调整 maintenance_work_mem 来处理大表
VACUUM ANALYZE large_table;
-- PG 18 自动使用增量式 VACUUM

存储引擎的改进

PG 18 引入了 WAL 预分配优化,在高并发写入场景下减少 WAL 锁竞争:

-- PG 18 的预写式日志优化配置
ALTER SYSTEM SET wal_buffers = '64MB';  -- 自动调整
ALTER SYSTEM SET wal_writer_delay = '50ms';  -- 更精细的控制

实际测试显示,在 64 核服务器上的高并发写入场景(1000+ 并发连接),PG 18 的 TPS 比 PG 17 提升了约 20-30%。

增量物化视图

PG 18 的重要新特性之一是增量物化视图。它解决了传统物化视图最大的痛点——全量刷新:

-- PG 18:创建增量物化视图
CREATE INCREMENTAL MATERIALIZED VIEW daily_order_summary AS
SELECT 
    DATE(created_at) as order_date,
    COUNT(*) as order_count,
    SUM(amount) as total_amount
FROM orders
GROUP BY DATE(created_at);

-- 增量刷新(只处理新增/变更的数据,不是全表扫描)
REFRESH MATERIALIZED VIEW INCREMENTALLY daily_order_summary;

对于数据仓库和报表场景,这可以大大降低刷新成本。传统物化视图在 1000 万行上刷新可能需要 30 秒,增量刷新只需要处理增量数据 1000 行的话,耗时一般在 100ms 以内。

SQL 标准的跟进

PG 18 在 SQL 标准兼容性方面也做了不少改进:

-- SQL:2023 标准的 NULL 排序控制
SELECT * FROM users ORDER BY name NULLS FIRST;

-- MERGE 的 RETURNING 子句
MERGE INTO target t
USING source s ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.name = s.name
WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name)
RETURNING merge_action(), t.*;  -- PG 18 新增

MERGE 加上 RETURNING 是一个看似小但非常实用的改进——你再也不用在 UPSERT 之后单独写一条查询来获取结果了。

安全的增强

PG 18 引入了原生的 OAuth 2.0 认证支持:

# pg_hba.conf - PG 18 OAuth 认证
hostssl all all 0.0.0.0/0 oauth oauth_validator_libraries='my_oauth_lib'

这意味着可以直接使用 OAuth 2.0 token 来认证数据库连接,不需要额外的中间层。对于使用云原生身份验证的企业来说,这消除了一个常见的中间代理层。

此外,PG 18 还增强了对加密连接的审计能力:pg_stat_ssl 视图现在可以显示每个连接的加密协议版本和加密套件。

升级前的准备工作

实际升级不是一个命令就能搞定的。以下是我在生产环境中升级总结的检查清单。

检查兼容性

-- 在 PG 17 上运行兼容性检查
SELECT name, setting, unit,
    CASE 
        WHEN name IN ('autovacuum', 'archive_mode', 'wal_level') 
        THEN 'check'
        ELSE 'ok'
    END as status
FROM pg_settings
WHERE category LIKE 'Version%' OR category LIKE 'Replication%';

-- 检查扩展兼容性
SELECT e.extname, e.extversion, 
       n.nspname as schema,
       e.extrelocatable
FROM pg_extension e
JOIN pg_namespace n ON e.extnamespace = n.oid;

需要特别注意的扩展

扩展 PG 18 状态 注意事项
pgvector ✅ 兼容(核心扩展) 需要升级到最新版 0.8+
PostGIS ✅ 兼容 3.5+ 版本支持
TimescaleDB ⚠️ 部分 需要 2.17+ 版本
pg_partman ⚠️ 需要更新 PG 18 分区系统有变化
citus ✅ 兼容 12.1+ 版本
pg_cron ✅ 兼容 1.6+ 版本

数据完整性检查

升级前做一次完整的数据完整性检查:

-- 检查损坏的索引
SELECT pg_check_table('large_table_name');

-- 检查损坏的页面
SELECT * FROM pg_stat_get_broken_page_count();

-- 检查 orphaned 数据
SELECT relname, n_dead_tup 
FROM pg_stat_user_tables 
WHERE n_dead_tup > 100000 
ORDER BY n_dead_tup DESC;

备份策略

我推荐的做法是:三份备份,两种技术

# 1. pg_dump 逻辑备份(粒度细)
pg_dump -Fc -Z 9 -j 4 mydb > mydb_$(date +%Y%m%d).dump

# 2. pg_basebackup 物理备份(恢复快)
pg_basebackup -D /backup/pg18_upgrade -Ft -z -P

# 3. 验证备份完整性
pg_restore -l mydb_$(date +%Y%m%d).dump > /dev/null && echo "备份验证通过"

逻辑备份的恢复粒度更细(可以恢复单表),物理备份的恢复速度更快。两者互补而不是替代。

升级路径选择

PG 18 支持从 17 直接用 pg_upgrade 升级,也支持通过逻辑复制滚动升级:

# 方法一:pg_upgrade(停机升级,推荐小网站)
pg_upgrade \
  --old-datadir /var/lib/pgsql/17/data \
  --new-datadir /var/lib/pgsql/18/data \
  --old-bindir /usr/pgsql-17/bin \
  --new-bindir /usr/pgsql-18/bin \
  --link \
  --jobs 4

# 方法二:逻辑复制(零停机)
# 1. 安装 PG 18 作为从库
# 2. 建立逻辑复制订阅
# 3. 等追平后切换 DNS
# 4. 关闭旧 PG 17 实例

# 方法三:Docker 迁移
docker run --name pg18-upgrade \
  -v pg17-data:/var/lib/postgresql/17/data:ro \
  -v pg18-data:/var/lib/postgresql/18/data \
  docker.io/pgautoupgrade/pgautoupgrade:18

我个人的建议:如果允许 5-10 分钟停机,pg_upgrade --link 是最简单可靠的方案。对于 7x24 的线上服务,逻辑复制是更安全的选择。

pg_upgrade 实际升级过程

以下是一次完整的升级记录,从开始到验证通过大约 15 分钟。

停止旧实例

# 1. 停掉需要升级的 PG 17 实例
systemctl stop postgresql-17

# 2. 确认已停止
systemctl status postgresql-17
# ● postgresql-17.service - PostgreSQL 17 database server
#    Active: inactive (dead)

# 3. 创建 PG 18 数据目录
mkdir -p /var/lib/pgsql/18/data
chown postgres:postgres /var/lib/pgsql/18/data

初始化新集群

# 初始化 PG 18 的空数据目录
su - postgres -c "/usr/pgsql-18/bin/initdb -D /var/lib/pgsql/18/data"

# 重要:复制旧的配置参数
# pg_upgrade 不会自动迁移 postgresql.conf 和 pg_hba.conf
cp /var/lib/pgsql/17/data/postgresql.conf /var/lib/pgsql/18/data/
cp /var/lib/pgsql/17/data/pg_hba.conf /var/lib/pgsql/18/data/

运行升级

# 以 postgres 用户运行(必须是同一个用户)
su - postgres -c "
pg_upgrade \
  --old-datadir /var/lib/pgsql/17/data \
  --new-datadir /var/lib/pgsql/18/data \
  --old-bindir /usr/pgsql-17/bin \
  --new-bindir /usr/pgsql-18/bin \
  --link \
  --jobs \$(nproc)
"

如果一切顺利,输出应该像这样:

Performing Consistency Checks
-----------------------------
Checking cluster versions            ok
Checking database connection settings ok
Checking database user names         ok
Checking for prepared transactions   ok
Checking for reg* data types in user tables ok
Checking for incompatible extensions  ok
Checking for tables WITH OIDS        ok

Upgrade Complete
----------------
Optimizer statistics are not transferred by pg_upgrade.
Once you start the new server, consider running:
    /usr/pgsql-18/bin/vacuumdb --all --analyze-only

升级后处理

# 1. 重新收集统计信息(必须做)
su - postgres -c "/usr/pgsql-18/bin/vacuumdb --all --analyze-only"

# 2. 启动新集群
systemctl start postgresql-18

# 3. 验证版本和扩展
psql -c "SELECT version();"
# PostgreSQL 18.0 on x86_64-pc-linux-gnu

# 4. 运行应用测试
psql -d mydb -c "SELECT COUNT(*) FROM users;"  # 基本验证

# 5. 清理旧备份(确认运行正常后再删)
# rm -rf /var/lib/pgsql/17/data.old

PG 18 新特性实战

升级完成后,可以开始真正利用 PG 18 的新特性了。

增量物化视图的实战用法

假设你有一个订单表,每天约 10 万条新增数据,需要为 BI 报表做聚合:

-- PG 18 的增量物化视图
CREATE INCREMENTAL MATERIALIZED VIEW IF NOT EXISTS daily_sales AS
SELECT 
    DATE(created_at) as sale_date,
    product_id,
    COUNT(*) as units_sold,
    SUM(price * quantity) as revenue
FROM orders
WHERE status = 'completed'
GROUP BY DATE(created_at), product_id;

-- 每天凌晨刷新(只处理昨天的新数据)
CREATE OR REPLACE FUNCTION refresh_sales_mv()
RETURNS void AS $$
BEGIN
    REFRESH MATERIALIZED VIEW INCREMENTALLY daily_sales;
END;
$$ LANGUAGE plpgsql;

-- 创建定时任务
SELECT cron.schedule('refresh-sales', '0 3 * * *', 
    'SELECT refresh_sales_mv();');

与传统的全量刷新相比,增量刷新只扫描变更的数据页,性能优势非常明显。对于一个 5000 万行的订单表,全量刷新耗时约 3 分钟,而增量刷新只需要 50ms。

利用并行查询加速 ETL

PG 18 的并行 INSERT 可以显著加快 ETL 速度:

-- 设置并行度
SET max_parallel_workers_per_gather = 4;
SET parallel_leader_participation = on;

-- 并行数据加载(PG 17 不支持这里的 SELECT 并行)
INSERT INTO analytics.aggregated_daily (
    user_id, 
    page_views, 
    unique_sessions
)
SELECT 
    user_id,
    COUNT(*) as page_views,
    COUNT(DISTINCT session_id) as unique_sessions
FROM events
WHERE event_date = CURRENT_DATE - 1
GROUP BY user_id;

在适当的硬件上,这个查询比 PG 17 快 3-4 倍。

JSON 和全文搜索的增强

PG 18 对 JSON 处理做了改进,现在 jsonb_path_query 的性能提升了约 40%:

-- PG 18 JSON 路径查询性能提升
SELECT jsonb_path_query(
    '{"users": [{"name": "张三", "tags": ["dev", "rust"]}, {"name": "李四", "tags": ["ai", "python"]}]}',
    '$.users[*] ? (@.tags[*] == "rust")'
);

此外,PG 18 支持了 SQL/JSON 标准中的 JSON_TABLE

-- JSON_TABLE:将 JSON 展开为关系表
SELECT jt.*
FROM api_responses,
JSON_TABLE(response_body, '$.items[*]' COLUMNS (
    id INT PATH '$.id',
    name TEXT PATH '$.name',
    price NUMERIC(10,2) PATH '$.price'
)) AS jt;

常见问题排查

升级过程中和升级后可能遇到的一些典型问题。

升级后性能下降

这是升级后最常见的反馈。原因通常是统计信息不足。

-- 检查统计信息是否完整
SELECT schemaname, tablename, 
       last_analyze, last_autoanalyze
FROM pg_stat_all_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY last_analyze DESC NULLS LAST;

-- 如果有表超过一周没分析,手动触发
ANALYZE VERBOSE my_table;

另一个可能的原因是新的查询计划选择了次优路径:

-- 禁用 PG 18 的新特性跑一次对比测试
SET enable_incremental_sort = off;
EXPLAIN (ANALYZE, BUFFERS) SELECT ...;  -- 你的慢查询
SET enable_incremental_sort = on;
EXPLAIN (ANALYZE, BUFFERS) SELECT ...;  -- 同上

-- 如果关闭后更快,说明查询计划器选错了
-- 可以调整相关配置参数或优化索引

扩展不兼容

-- 启动后检查扩展状态
SELECT * FROM pg_extension WHERE extversion IS NULL;

-- 如果遇到不兼容扩展
DROP EXTENSION IF EXISTS problematic_ext CASCADE;
-- 安装新版本
CREATE EXTENSION IF NOT EXISTS problematic_ext;

连接数问题

PG 18 调整了默认连接处理机制,如果你的应用依赖大量短连接:

-- 检查当前连接使用情况
SELECT count(*) as total_connections,
       sum(CASE WHEN state = 'active' THEN 1 ELSE 0 END) as active,
       sum(CASE WHEN state = 'idle' THEN 1 ELSE 0 END) as idle,
       sum(CASE WHEN state = 'idle in transaction' THEN 1 ELSE 0 END) as idle_in_txn
FROM pg_stat_activity;

-- 建议使用连接池(pgbouncer / pgcat)
-- PG 18 对连接池的支持更好

升级还是不升级

升级决策可以结合自身情况来判断:

建议升级的情况: - 数据库是 ETL/分析型负载,并行查询能显著提速 - 需要增量物化视图来优化报表性能 - 有大量 DML 操作,VACUUM 开销影响业务 - 当前在 PG 15 或更早,直接跳级到 PG 18 会有质的提升

可以等的: - 系统稳定运行,没有性能瓶颈 - 依赖的扩展在 PG 18 上还不完全支持 - 对零停机有严格要求,逻辑复制方案还没准备好

不建议升级的: - 应用即将大规模重构 - 依赖 PG 17 中已知在 PG 18 中被移除的特性(虽然这种情况很少)

结语

PG 17 → 18 不是一个颠覆性的升级,但它是扎实的增量改进。增量物化视图、并行查询增强、OAuth 认证——每个改进单独看都不是「必须升级」的级别,但它们加在一起,构成了一个更现代、更高效的数据库。

对于正在考虑升级的用户,我的建议是:先在预发布环境跑一个月,把查询日志过一遍,确认没有回归问题后再上生产。


发表于 2026-07-19 · 数据库 · #PostgreSQL #数据库 #DevOps #性能优化