PostgreSQL 19 深度实战:当三十年老牌数据库迎来图查询革命——从 SQL/PGQ 到 REPACK 并行重组、在线校验和与生产级迁移的完全指南(2026)
2026 年 6 月 4 日,PostgreSQL 全球开发组正式发布了 PostgreSQL 19 Beta 1。这个版本距离 PostgreSQL 18 正式发布不到一年,却带来了堪称革命性的特性升级:原生图查询支持、时间序列操作语法、在线校验和切换、并行自动清理、SIMD 加速数据导入、REPACK 在线表重组、分区合并/拆分……每一个特性都在直击生产环境的真实痛点。
PostgreSQL 已经走过了整整 30 年(1996-2026),从 Berkeley POSTGRES 项目演变为全球最先进的开源关系数据库。而 PG19 的发布,标志着一个重要转折:PostgreSQL 不再仅仅是一个关系数据库——它正在成为一个多模态数据库平台,原生支持图查询、时态数据、JSON 向量搜索等现代数据工作负载。
本文将从以下几个维度,对 PostgreSQL 19 的核心特性进行深度、实战导向的全面解析:
- SQL/PGQ 图查询:SQL:2023 标准的原生图查询支持,告别独立图数据库
- 时态数据操作:UPDATE/DELETE FOR PORTION OF 的完整实战
- ON CONFLICT DO SELECT:原子性"获取或创建"模式
- REPACK 命令:替代 VACUUM FULL 和 CLUSTER 的在线表重组
- 并行自动清理:autovacuum 的性能飞跃
- 在线校验和:零停机数据完整性保障
- SIMD 加速与 Radix Sort:底层数据处理性能突破
- 逻辑复制增强:序列同步、分区排除、WAIT FOR LSN
- 优化器改进:反连接转换、预聚合下推、常量折叠增强
- 破坏性变更与迁移指南:从 PG18 升级的完整路径
一、SQL/PGQ:图查询的革命性突破
1.1 为什么图查询如此重要
图数据库(Neo4j、JanusGraph 等)长期以来在社交网络分析、推荐系统、欺诈检测、知识图谱等场景中扮演着关键角色。然而,维护一个独立的图数据库意味着额外的运维成本、数据同步复杂性和学习曲线。
PostgreSQL 19 通过实现 ISO SQL:2023 第 16 部分的 SQL/PGQ(SQL Property Graph Queries) 标准,将图查询能力直接嵌入到关系数据库核心中。这意味着你可以在现有的关系表上定义属性图,使用模式匹配语法进行图遍历查询——无需引入任何外部图数据库。
1.2 属性图的基本概念
属性图由两种元素构成:
- 顶点(Vertex):代表实体(如用户、商品、文章)
- 边(Edge):代表实体之间的关系(如关注、购买、引用)
每个顶点和边都可以携带属性(如用户名、创建时间、关系类型)。
1.3 在现有表上定义属性图
-- 创建示例数据表
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE follows (
follower_id INT REFERENCES users(id) ON DELETE CASCADE,
followed_id INT REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (follower_id, followed_id)
);
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
author_id INT REFERENCES users(id) ON DELETE CASCADE,
title TEXT NOT NULL,
content TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE likes (
user_id INT REFERENCES users(id) ON DELETE CASCADE,
post_id INT REFERENCES posts(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (user_id, post_id)
);
-- 插入测试数据
INSERT INTO users (name, email) VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com'),
('David', 'david@example.com'),
('Eve', 'eve@example.com');
INSERT INTO follows (follower_id, followed_id) VALUES
(1, 2), -- Alice follows Bob
(1, 3), -- Alice follows Charlie
(2, 3), -- Bob follows Charlie
(2, 4), -- Bob follows David
(3, 5), -- Charlie follows Eve
(4, 1); -- David follows Alice
定义属性图:
CREATE PROPERTY GRAPH social_graph
VERTEX TABLES (
users LABEL person PROPERTIES (id, name, email)
)
EDGE TABLES (
follows
SOURCE KEY (follower_id) REFERENCES users (id)
DESTINATION KEY (followed_id) REFERENCES users (id)
LABEL follows
PROPERTIES (created_at)
);
关键设计思想:属性图是对现有关系表的"视图层",不复制数据,不改变存储结构。定义图后,底层数据的增删改查完全通过原有 SQL 操作完成。
1.4 图查询语法:MATCH 模式匹配
SQL/PGQ 的核心查询函数是 GRAPH_TABLE(),它使用 MATCH 子句进行模式匹配:
-- 查询 1:Alice 关注了谁?
SELECT *
FROM GRAPH_TABLE (
social_graph
MATCH (a IS person WHERE a.name = 'Alice') -[IS follows]->(b IS person)
COLUMNS (b.name AS friend_name, b.email AS friend_email)
);
-- 查询 2:朋友的朋友(二度关系)
SELECT *
FROM GRAPH_TABLE (
social_graph
MATCH
(a IS person WHERE a.name = 'Alice')
-[IS follows]->(b IS person)
-[IS follows]->(c IS person)
COLUMNS (
b.name AS mutual_friend,
c.name AS foaf_name
)
);
-- 查询 3:推荐系统 —— 找到二度关系中 Alice 不直接关注的人
SELECT foaf_name, COUNT(*) AS mutual_count
FROM GRAPH_TABLE (
social_graph
MATCH
(a IS person WHERE a.name = 'Alice')
-[IS follows]->(b IS person)
-[IS follows]->(c IS person)
COLUMNS (c.name AS foaf_name)
)
WHERE foaf_name != 'Alice'
AND foaf_name NOT IN (
SELECT friend_name FROM GRAPH_TABLE (
social_graph
MATCH (a IS person WHERE a.name = 'Alice') -[IS follows]->(b IS person)
COLUMNS (b.name AS friend_name)
)
)
GROUP BY foaf_name
ORDER BY mutual_count DESC;
1.5 图查询的执行原理
PostgreSQL 19 将 SQL/PGQ 查询内部重写为标准关系查询。这意味着:
- 图查询可以利用现有的 B-tree、Hash、GiST 等索引
- 查询优化器能对图遍历进行代价估算和执行计划优化
- 可以与普通 SQL 查询进行 JOIN、子查询等组合操作
-- 图查询与关系查询的混合使用
SELECT
g.foaf_name,
g.mutual_count,
p.title AS latest_post
FROM (
SELECT foaf_name, COUNT(*) AS mutual_count
FROM GRAPH_TABLE (
social_graph
MATCH
(a IS person WHERE a.name = 'Alice')
-[IS follows]->(b IS person)
-[IS follows]->(c IS person)
COLUMNS (c.name AS foaf_name)
)
WHERE foaf_name != 'Alice'
GROUP BY foaf_name
) g
LEFT JOIN LATERAL (
SELECT p.title
FROM posts p
JOIN users u ON u.id = p.author_id
WHERE u.name = g.foaf_name
ORDER BY p.created_at DESC
LIMIT 1
) p ON true
ORDER BY g.mutual_count DESC;
1.6 实际应用场景
场景一:社交网络推荐
-- 基于共同关注度的用户推荐
CREATE OR REPLACE FUNCTION recommend_users(p_user_name TEXT)
RETURNS TABLE (recommended_name TEXT, score NUMERIC) AS $$
BEGIN
RETURN QUERY
WITH graph_result AS (
SELECT b.name AS direct_friend, c.name AS candidate, COUNT(*) OVER (PARTITION BY c.name) AS common_friends
FROM GRAPH_TABLE (
social_graph
MATCH
(a IS person WHERE a.name = p_user_name)
-[IS follows]->(b IS person)
-[IS follows]->(c IS person)
COLUMNS (b.name AS direct_friend, c.name AS candidate)
)
WHERE c.name != p_user_name
)
SELECT DISTINCT candidate, MAX(common_friends) AS score
FROM graph_result
WHERE candidate NOT IN (
SELECT friend_name FROM GRAPH_TABLE (
social_graph
MATCH (x IS person WHERE x.name = p_user_name) -[IS follows]->(y IS person)
COLUMNS (y.name AS friend_name)
)
)
GROUP BY candidate
ORDER BY score DESC;
END;
$$ LANGUAGE plpgsql;
-- 使用
SELECT * FROM recommend_users('Alice');
场景二:欺诈检测
-- 在金融交易图中检测可疑环状交易路径
CREATE PROPERTY GRAPH transaction_graph
VERTEX TABLES (
accounts LABEL account PROPERTIES (id, owner_name, balance),
merchants LABEL merchant PROPERTIES (id, name, category)
)
EDGE TABLES (
transfers
SOURCE KEY (from_account) REFERENCES accounts(id)
DESTINATION KEY (to_account) REFERENCES accounts(id)
LABEL transfer
PROPERTIES (amount, created_at)
);
-- 检测 3 跳以内的环状转账(A→B→C→A)
SELECT
a.owner_name AS from_account,
b.owner_name AS via_account,
c.owner_name AS to_account,
SUM(t1.amount + t2.amount + t3.amount) AS cycle_amount
FROM GRAPH_TABLE (
transaction_graph
MATCH
(a IS account)
-[IS transfer WHERE t1.amount > 10000]->(b IS account)
-[IS transfer WHERE t2.amount > 10000]->(c IS account)
-[IS transfer WHERE t3.amount > 10000]->(d IS account WHERE d.id = a.id)
COLUMNS (
a.owner_name, b.owner_name, c.owner_name,
t1.amount AS t1_amount, t2.amount AS t2_amount, t3.amount AS t3_amount
)
) sub;
场景三:知识图谱与内容推荐
-- 文章标签关联图中的内容推荐
CREATE PROPERTY GRAPH content_graph
VERTEX TABLES (
articles LABEL article PROPERTIES (id, title, category),
tags LABEL tag PROPERTIES (id, name)
)
EDGE TABLES (
article_tags
SOURCE KEY (article_id) REFERENCES articles(id)
DESTINATION KEY (tag_id) REFERENCES tags(id)
LABEL tagged_with
);
-- 基于标签相似度推荐文章
SELECT DISTINCT recommended_title, tag_count
FROM (
SELECT
b.title AS recommended_title,
COUNT(*) AS tag_count
FROM GRAPH_TABLE (
content_graph
MATCH
(a IS article WHERE a.title = 'PostgreSQL 19 新特性')
-[IS tagged_with]->(t IS tag)
<-[IS tagged_with]-(b IS article WHERE b.title != 'PostgreSQL 19 新特性')
COLUMNS (b.title AS recommended_title)
)
GROUP BY recommended_title
) sub
ORDER BY tag_count DESC
LIMIT 10;
二、UPDATE/DELETE FOR PORTION OF:时态数据的精准手术刀
2.1 问题背景
时态数据(Temporal Data)在业务系统中无处不在:定价策略、合同有效期、员工任职记录、促销活动……传统 SQL 处理时间区间的方式非常笨拙——修改某个时间段的价格需要手动拆分行、插入新行、删除旧行,代码复杂且容易出错。
SQL:2011 引入了时态数据标准,而 PostgreSQL 19 终于实现了 UPDATE/DELETE FOR PORTION OF 语法,让时态操作变得直观且安全。
2.2 语法详解
-- 基础示例:价格策略表
CREATE TABLE pricing (
product_id INT,
price NUMERIC(10,2),
valid_from DATE NOT NULL,
valid_to DATE NOT NULL,
CONSTRAINT period_overlap EXCLUDE (
product_id WITH =,
daterange(valid_from, valid_to, '[]') WITH &&
),
PRIMARY KEY (product_id, valid_from, valid_to)
);
-- 插入历史价格数据
INSERT INTO pricing (product_id, price, valid_from, valid_to) VALUES
(1, 99.00, '2026-01-01', '2026-03-31'), -- Q1: 99 元
(1, 129.00, '2026-04-01', '2026-06-30'), -- Q2: 129 元
(1, 119.00, '2026-07-01', '2026-12-31'); -- H2: 119 元
2.3 UPDATE FOR PORTION OF 实战
-- 场景:5 月举办促销,将价格从 129 调整为 109
-- 这会自动将 Q2 的行拆分为两部分:4 月保持 129,5 月改为 109,6 月恢复 129
UPDATE pricing
SET price = 109.00
FOR PORTION OF app_period FROM DATE '2026-05-01' TO DATE '2026-06-01'
WHERE product_id = 1
AND valid_from <= DATE '2026-05-01'
AND valid_to >= DATE '2026-06-01';
-- 查看结果:自动拆分为 3 行
SELECT * FROM pricing WHERE product_id = 1 ORDER BY valid_from;
-- 结果:
-- product_id | price | valid_from | valid_to
-- -----------+-------+------------+-----------
-- 1 | 99.00 | 2026-01-01 | 2026-03-31
-- 1 |129.00 | 2026-04-01 | 2026-04-30 (保持原价)
-- 1 |109.00 | 2026-05-01 | 2026-05-31 (促销价)
-- 1 |129.00 | 2026-06-01 | 2026-06-30 (恢复原价)
-- 1 |119.00 | 2026-07-01 | 2026-12-31
2.4 DELETE FOR PORTION OF 实战
-- 删除某段时间的记录(如数据合规要求的删除请求)
DELETE FROM pricing
FOR PORTION OF del_period FROM DATE '2026-04-01' TO DATE '2026-06-01'
WHERE product_id = 1
AND valid_from <= DATE '2026-04-01'
AND valid_to >= DATE '2026-06-01';
-- 结果:Q2 的行被删除,但 Q1 和 H2 的行保留
SELECT * FROM pricing WHERE product_id = 1 ORDER BY valid_from;
2.5 边界情况与触发器交互
重要注意事项:FOR PORTION OF 操作会在执行过程中创建新行(拆分产生的行),这些新行会触发行级触发器。在时态表上使用外键和级联操作时,需要仔细测试。
-- 安全的审计触发器示例
CREATE OR REPLACE FUNCTION audit_temporal_changes()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO pricing_audit (product_id, old_price, new_price, changed_by, changed_at, period)
VALUES (
NEW.product_id,
OLD.price,
NEW.price,
current_user,
now(),
tstzrange(NEW.valid_from, NEW.valid_to, '[]')
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 注意:FOR PORTION OF 拆分产生的新行会触发此触发器
-- 确保触发器能正确处理拆分产生的行
三、ON CONFLICT DO SELECT:原子性的"获取或创建"
3.1 问题背景
"获取或创建"(Get-or-Create)是并发系统中极其常见的模式:检查记录是否存在,不存在则插入。传统实现需要 SELECT + INSERT 两条语句,在高并发场景下存在竞态条件(TOCTOU 问题)。
PostgreSQL 9.5 引入了 ON CONFLICT DO UPDATE(UPSERT),但无法直接获取冲突行。PG19 新增的 ON CONFLICT DO SELECT ... RETURNING 完美解决了这个问题。
3.2 语法与实战
-- 场景:分布式系统中获取或创建租户
CREATE TABLE tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT UNIQUE NOT NULL,
plan TEXT DEFAULT 'free',
created_at TIMESTAMPTZ DEFAULT now()
);
-- 旧方式(存在竞态条件):
-- 1. SELECT * FROM tenants WHERE name = 'Acme Corp';
-- 2. 如果不存在,INSERT ...
-- PG19 新方式(原子性操作):
INSERT INTO tenants (name, plan)
VALUES ('Acme Corp', 'enterprise')
ON CONFLICT (name) DO SELECT
RETURNING id, name, plan, created_at;
-- 成功插入时返回新行
-- 冲突时返回已存在的行
3.3 可选的行锁定
-- 获取或创建 + 行级锁(防止并发修改)
INSERT INTO tenants (name, plan)
VALUES ('Acme Corp', 'enterprise')
ON CONFLICT (name) DO SELECT
FOR UPDATE
RETURNING id, name, plan;
3.4 实际业务应用
-- 幂等的优惠券领取
CREATE TABLE coupons (
code TEXT PRIMARY KEY,
description TEXT,
discount NUMERIC(5,2),
total_count INT,
claimed INT DEFAULT 0
);
CREATE TABLE coupon_claims (
user_id INT,
code TEXT REFERENCES coupons(code),
claimed_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (user_id, code)
);
CREATE OR REPLACE FUNCTION claim_coupon(p_user_id INT, p_code TEXT)
RETURNS TABLE (code TEXT, discount NUMERIC, claimed_at TIMESTAMPTZ) AS $$
DECLARE
v_discount NUMERIC;
BEGIN
-- 原子性:领取记录存在则返回,不存在则创建
INSERT INTO coupon_claims (user_id, code)
VALUES (p_user_id, p_code)
ON CONFLICT (user_id, code) DO SELECT
INTO coupon_claims;
-- 更新领取计数
SELECT discount INTO v_discount FROM coupons WHERE code = p_code;
RETURN QUERY
SELECT cc.code, c.discount, cc.claimed_at
FROM coupon_claims cc
JOIN coupons c ON c.code = cc.code
WHERE cc.user_id = p_user_id AND cc.code = p_code;
END;
$$ LANGUAGE plpgsql;
-- 使用
SELECT * FROM claim_coupon(42, 'SUMMER2026');
3.5 分布式 ID 生成
-- 幂等的序列号分配
CREATE TABLE sequences (
name TEXT PRIMARY KEY,
last_value BIGINT DEFAULT 0
);
CREATE OR REPLACE FUNCTION next_sequence_value(p_name TEXT)
RETURNS BIGINT AS $$
DECLARE
v_result BIGINT;
BEGIN
INSERT INTO sequences (name, last_value) VALUES (p_name, 1)
ON CONFLICT (name) DO UPDATE SET last_value = sequences.last_value + 1
RETURNING last_value INTO v_result;
RETURN v_result;
END;
$$ LANGUAGE plpgsql;
SELECT next_sequence_value('order_id');
四、REPACK:在线表重组的革命
4.1 痛点回顾
PostgreSQL 的 VACUUM FULL 和 CLUSTER 命令长期以来饱受诟病:
- VACUUM FULL:重写整个表,需要 ACCESS EXCLUSIVE 锁,阻塞所有读写
- CLUSTER:按索引物理排序,同样需要 ACCESS EXCLUSIVE 锁
- 对于生产环境的大表(GB/TB 级),这两种操作意味着不可接受的停机时间
4.2 REPACK 的设计
PostgreSQL 19 引入 REPACK 命令,统一并替代 VACUUM FULL 和 CLUSTER:
-- 基础 REPACK(等同于 VACUUM FULL + CLUSTER)
REPACK TABLE big_table;
-- 按索引排序的 REPACK(等同于 CLUSTER)
REPACK TABLE big_table USING INDEX big_table_pkey;
-- 在线 REPACK(不阻塞读写!)
REPACK TABLE big_table CONCURRENTLY;
4.3 REPACK CONCURRENTLY 的工作原理
REPACK CONCURRENTLY 的核心思想是无锁重建:
- 创建临时表,结构与原表相同
- 在临时表上建立与原表相同的索引
- 通过触发器同步增量变更
- 将原表数据批量复制到临时表
- 应用增量变更到临时表
- 短暂获取 ACCESS EXCLUSIVE 锁,交换表名
- 删除旧表
-- 生产环境的 REPACK 操作示例
-- 步骤 1:检查表膨胀程度
SELECT
schemaname || '.' || relname AS table_name,
pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
pg_size_pretty(pg_relation_size(relid)) AS table_size,
pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) AS toast_and_index_size,
round(100.0 * pg_stat_get_dead_tuples(relid) / NULLIF(pg_stat_get_live_tuples(relid) + pg_stat_get_dead_tuples(relid), 0), 2) AS dead_pct
FROM pg_stat_user_tables
WHERE pg_stat_get_dead_tuples(relid) > 10000
ORDER BY (pg_stat_get_dead_tuples(relid)::NUMERIC / NULLIF(pg_stat_get_live_tuples(relid), 0)) DESC
LIMIT 10;
-- 步骤 2:对膨胀严重的表执行在线 REPACK
REPACK TABLE orders CONCURRENTLY;
-- 步骤 3:监控 REPACK 进度
SELECT * FROM pg_stat_progress_cluster;
-- 步骤 4:配置 REPACK 相关参数
-- postgresql.conf
-- max_repack_replication_slots = 10 -- 最大并发 REPACK 数
4.4 REPACK vs VACUUM vs CLUSTER 对比
| 特性 | VACUUM | VACUUM FULL | CLUSTER | REPACK | REPACK CONCURRENTLY |
|---|---|---|---|---|---|
| 锁级别 | 共享锁 | ACCESS EXCLUSIVE | ACCESS EXCLUSIVE | ACCESS EXCLUSIVE | 无阻塞 |
| 回收空间 | 标记为可复用 | 回收到 OS | 回收到 OS | 回收到 OS | 回收到 OS |
| 表排序 | 否 | 否 | 按索引排序 | 可选 | 可选 |
| 中断恢复 | - | 重新开始 | 重新开始 | 重新开始 | 可恢复 |
| 生产可用性 | ✅ 高 | ❌ 停机 | ❌ 停机 | ❌ 停机 | ✅ 高 |
五、并行自动清理:autovacuum 性能飞跃
5.1 为什么需要并行 autovacuum
自动清理(autovacuum)是 PostgreSQL 生命周期管理的核心机制。对于拥有大量索引的宽表,索引清理往往是 autovacuum 的性能瓶颈——因为索引清理是串行执行的。
5.2 PG19 的并行 autovacuum
-- 启用并行 autovacuum(集群级)
-- postgresql.conf
autovacuum_max_parallel_workers = 2;
-- 单表配置
ALTER TABLE orders SET (autovacuum_parallel_workers = 2);
工作原理:并行 Worker 来自现有的 max_parallel_maintenance_workers 池,每个 Worker 处理不同的索引,实现索引清理的并行化。
5.3 内存考量
最大内存消耗 = autovacuum_max_workers × autovacuum_max_parallel_workers × maintenance_work_mem
生产配置建议:
-- 保守配置(默认)
autovacuum_max_workers = 3
autovacuum_max_parallel_workers = 2
maintenance_work_mem = 64MB
-- 最大消耗:3 × 2 × 64MB = 384MB
-- 激进配置(大表多索引场景)
autovacuum_max_workers = 6
autovacuum_max_parallel_workers = 4
maintenance_work_mem = 256MB
-- 最大消耗:6 × 4 × 256MB = 6GB
-- ⚠️ 升级前务必评估内存预算!
5.4 新增的评分系统
PG19 引入了 autovacuum 评分系统,让 DBA 可以更精细地控制清理优先级:
-- postgresql.conf
autovacuum_freeze_score_weight = 1.0 -- 冻结回收权重
autovacuum_multixact_freeze_score_weight = 1.0 -- 多事务冻结权重
autovacuum_vacuum_score_weight = 1.0 -- 普通清理权重
vacuum_insert_score_weight = 0.5 -- 插入清理权重
autovacuum_analyze_score_weight = 0.2 -- 统计分析权重
-- 监控评分
SELECT * FROM pg_stat_autovacuum_scores;
六、在线校验和:零停机数据完整性保障
6.1 历史痛点
数据校验和(Data Checksums)是检测静默数据损坏的最后防线。但 PostgreSQL 之前的版本中,校验和只能在 initdb 时启用,且运行时无法更改——关闭数据库、运行 pg_checksums 工具、再启动数据库。
6.2 PG19 的在线校验和
-- 查看当前校验和状态
SELECT name, setting FROM pg_settings WHERE name = 'data_checksums';
-- 在线启用校验和(无需停机!)
ALTER SYSTEM SET data_checksums = on;
SELECT pg_reload_conf();
-- 在线禁用校验和
ALTER SYSTEM SET data_checksums = off;
SELECT pg_reload_conf();
内部实现:在线切换校验和通过后台 Worker 进程逐步修改数据页的校验和标记,不影响正常的读写操作。
6.3 生产环境建议
-- 强烈建议在新部署中启用校验和
-- 检测静默数据损坏的唯一可靠手段
-- 修复已有集群(无需停机)
ALTER SYSTEM SET data_checksums = on;
SELECT pg_reload_conf();
-- 监控校验和切换进度
-- 可通过 pg_stat_progress_basebackup 视图观察
SELECT * FROM pg_stat_progress_basebackup;
七、SIMD 加速与 Radix Sort:底层性能突破
7.1 SIMD 加速 COPY FROM
PostgreSQL 19 使用 SIMD(Single Instruction Multiple Data)CPU 指令加速文本和 CSV 数据导入:
-- 传统方式
COPY orders FROM '/data/orders.csv' WITH (FORMAT csv, HEADER true);
-- PG19 中的性能提升
-- 文本解析使用 SIMD 指令(如 x86 的 AVX2/AVX-512)
-- 对于百万级数据导入,性能提升 20-40%
-- 优化建议
SET work_mem = '256MB'; -- 增加工作内存
SET maintenance_work_mem = '1GB'; -- 增加维护内存
COPY orders FROM '/data/orders.csv' WITH (FORMAT csv, HEADER true, PARALLEL true);
7.2 Radix Sort 替代快速排序
PG19 将内部排序算法从快速排序升级为 Radix Sort(基数排序):
- 时间复杂度:O(n × k),其中 k 是键长度(对于固定宽度类型如 INT,k 是常数)
- 对比快速排序:O(n × log n),但实际常数因子小
-- 受益场景:大量数据的排序操作
-- 1. ORDER BY 整数主键
SELECT * FROM orders ORDER BY id LIMIT 100;
-- 2. DISTINCT 整数值
SELECT DISTINCT user_id FROM events;
-- 3. GROUP BY 整数列
SELECT user_id, COUNT(*) FROM events GROUP BY user_id;
-- 4. 索引构建(B-tree 使用排序)
CREATE INDEX CONCURRENTLY idx_events_user_id ON events(user_id);
-- 5. MergeAppend 的增量排序
SELECT * FROM (
SELECT * FROM orders_2025
UNION ALL
SELECT * FROM orders_2026
) combined
ORDER BY created_at;
7.3 异步 I/O 改进
-- 新增 I/O Worker 自动管理
-- postgresql.conf
io_method = 'worker' -- 启用 worker 模式
io_min_workers = 2 -- 最小 Worker 数
io_max_workers = 8 -- 最大 Worker 数
io_worker_idle_timeout = '60s' -- Worker 空闲超时
io_worker_launch_interval = '100ms' -- Worker 启动间隔
7.4 默认压缩算法切换
-- TOAST 数据默认压缩从 pglz 切换为 lz4
-- lz4 压缩速度更快,解压速度更快,压缩率略低
-- 查看当前设置
SHOW default_toast_compression;
-- PG19 默认:lz4
-- 如需恢复 pglz(更高的压缩率)
SET default_toast_compression = 'pglz';
ALTER SYSTEM SET default_toast_compression = 'pglz';
八、查询增强特性
8.1 GROUP BY ALL
-- PG19 之前:需要手动列出所有非聚合列
SELECT department, location, COUNT(*), AVG(salary)
FROM employees
GROUP BY department, location;
-- PG19:自动推断
SELECT department, location, COUNT(*), AVG(salary)
FROM employees
GROUP BY ALL;
-- 自动将 department 和 location 加入 GROUP BY 子句
8.2 窗口函数 IGNORE NULLS
-- 查找最近的非 NULL 值(GAP-FILL 场景)
SELECT
ts,
value,
last_value(value) IGNORE NULLS OVER (ORDER BY ts) AS last_non_null,
first_value(value) IGNORE NULLS OVER (ORDER BY ts DESC) AS next_non_null
FROM sensor_data;
8.3 TID Range Scan 并行化
-- TID Range Scan 现在可以并行执行
-- 适用于直接定位物理行的场景
SELECT * FROM large_table WHERE ctid BETWEEN '(0,0)' AND '(1000,0)';
8.4 表扫描标记 all-visible
-- 查询扫描也可以标记页面为 all-visible
-- 之前只有 VACUUM 和 COPY FREEZE 能做
-- 这意味着普通查询也能帮助减少未来的 VACUUM 工作量
九、逻辑复制增强
9.1 序列同步
PG19 解决了逻辑复制中长期缺失的一个能力:序列值同步。
-- 发布端
CREATE PUBLICATION pub_all
ALL TABLES
ALL SEQUENCES; -- 新增:发布所有序列
-- 订阅端
CREATE SUBSCRIPTION sub_all
CONNECTION 'host=primary dbname=mydb'
PUBLICATION pub_all
WITH (copy_data = true);
-- 手动同步序列值
ALTER SUBSCRIPTION sub_all REFRESH SEQUENCES;
-- 查看同步状态
SELECT * FROM pg_stat_subscription_stats;
-- 新增列:sync_seq_error_count
9.2 分区排除语法
-- 发布排除特定分区
CREATE PUBLICATION pub_active
ALL TABLES
EXCEPT TABLE orders_archive, orders_pending_review;
9.3 WAIT FOR LSN
-- 在备库上等待特定 LSN 到达
-- 适用于需要确保数据一致性的场景
WAIT FOR LSN '0/1A2B3C4';
-- 等待特定阶段
WAIT FOR LSN '0/1A2B3C4' WRITE; -- 已写入 WAL
WAIT FOR LSN '0/1A2B3C4' FLUSH; -- 已刷到磁盘
WAIT FOR LSN '0/1A2B3C4' REPLAY; -- 已在备库回放
-- 应用场景:确保快照一致性备份
BEGIN;
SELECT pg_create_physical_replication_slot('backup_slot');
WAIT FOR LSN '0/1A2B3C4' REPLAY;
-- 执行备份操作
COMMIT;
9.4 冲突信息保留
-- 订阅端配置:保留冲突信息
ALTER SUBSCRIPTION sub_all SET (retain_conflict_info = true);
-- 查看冲突
SELECT * FROM pg_stat_subscription_conflicts;
-- 最大保留时间
ALTER SUBSCRIPTION sub_all SET (max_retention_duration = '7d');
十、优化器改进详解
10.1 NOT IN → ANTI JOIN
-- PG19 之前:NOT IN 通常使用 Hash Anti Join,但不处理 NULL
-- PG19:当确认 NULL 不存在时,NOT IN 也会被转换为 ANTI JOIN
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE user_id NOT IN (SELECT id FROM suspended_users WHERE id IS NOT NULL);
-- 可能的执行计划变化:
-- 旧:Hash Anti Join(需要额外的 NULL 过滤)
-- 新:直接 Anti Join(更高效)
10.2 更多 LEFT JOIN → ANTI JOIN 转换
-- NOT EXISTS 子查询
SELECT o.*
FROM orders o
WHERE NOT EXISTS (
SELECT 1 FROM returns r WHERE r.order_id = o.id
);
-- LEFT JOIN + WHERE IS NULL
SELECT o.*
FROM orders o
LEFT JOIN returns r ON r.order_id = o.id
WHERE r.id IS NULL;
-- PG19:两种写法都更积极地转换为 ANTI JOIN
10.3 聚合预计算下推
-- PG19 允许某些聚合在 JOIN 之前执行
-- 减少需要处理的行数
SELECT
d.department_name,
COUNT(*) AS emp_count,
AVG(e.salary) AS avg_salary
FROM departments d
JOIN employees e ON e.dept_id = d.id
GROUP BY d.department_name;
-- PG19 可能的优化:先计算 employees 的聚合,再与 departments JOIN
10.4 Memoize 用于 ANTI JOIN
-- Memoize 节点现在可用于 ANTI JOIN(内侧唯一时)
-- 适用于相关子查询场景
SELECT * FROM orders o
WHERE NOT EXISTS (
SELECT 1 FROM discounts d
WHERE d.product_id = o.product_id
AND d.min_qty <= o.quantity
);
十一、分区增强:MERGE/SPLIT PARTITIONS
11.1 分区合并
-- 将多个分区合并为一个
CREATE TABLE orders (
id SERIAL,
created_at TIMESTAMPTZ,
data JSONB
) PARTITION BY RANGE (created_at);
CREATE TABLE orders_2026q1 PARTITION OF orders
FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
CREATE TABLE orders_2026q2 PARTITION OF orders
FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');
-- 将 Q1 和 Q2 合并为 H1
ALTER TABLE orders MERGE PARTITIONS (orders_2026q1, orders_2026q2) INTO orders_2026h1;
11.2 分区拆分
-- 将一个大分区拆分为多个小分区
ALTER TABLE orders SPLIT PARTITION orders_2026h1
INTO (orders_2026q1, orders_2026q2)
FOR VALUES FROM ('2026-01-01') TO ('2026-07-01');
11.3 实际应用
-- 场景:日志表按月分区,但需要按周查询
-- 1. 创建周分区表
CREATE TABLE access_logs (
id BIGSERIAL,
ts TIMESTAMPTZ,
path TEXT,
status INT
) PARTITION BY RANGE (ts);
-- 2. 按月分区
CREATE TABLE access_logs_2026_06 PARTITION OF access_logs
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
-- 3. 当查询需要按周粒度时,拆分为周分区
ALTER TABLE access_logs SPLIT PARTITION access_logs_2026_06 INTO (
access_logs_2026_06_w1 FOR VALUES FROM ('2026-06-01') TO ('2026-06-08'),
access_logs_2026_06_w2 FOR VALUES FROM ('2026-06-08') TO ('2026-06-15'),
access_logs_2026_06_w3 FOR VALUES FROM ('2026-06-15') TO ('2026-06-22'),
access_logs_2026_06_w4 FOR VALUES FROM ('2026-06-22') TO ('2026-07-01')
);
十二、监控与可观测性增强
12.1 新增系统视图
-- 1. 锁类型统计
SELECT * FROM pg_stat_lock;
-- 2. 恢复状态
SELECT * FROM pg_stat_recovery;
-- 3. 自动清理评分
SELECT
relname,
n_dead_tup,
vacuum_score,
analyze_score,
freeze_score
FROM pg_stat_autovacuum_scores
WHERE schemaname = 'public'
ORDER BY vacuum_score DESC;
-- 4. Vacuum 进度详情(新增 started_by 和 mode 列)
SELECT
relname,
started_by, -- 'autovacuum' | 'manual' | 'autoanalyze'
mode, -- 'lazy' | 'full' | 'parallel'
phase,
heap_blks_total,
heap_blks_scanned,
heap_blks_vacuumed
FROM pg_stat_progress_vacuum;
12.2 进程级日志控制
-- 按进程类型设置不同日志级别
-- postgresql.conf
log_min_messages = 'warning' -- 默认
log_min_messages:autovacuum = 'debug5' -- autovacuum 详细日志
log_min_messages:walsender = 'notice' -- WAL 发送器日志
-- 新增:长时间 autoanalyze 日志
log_autoanalyze_min_duration = '1s'; -- 记录超过 1 秒的 ANALYZE
log_autovacuum_min_duration = '1s'; -- 记录超过 1 秒的 VACUUM
12.3 锁等待日志默认启用
-- log_lock_waits 现在默认为 on
-- 当会话等待锁超过 deadlock_timeout 时,会自动记录日志
SHOW log_lock_waits; -- PG19 默认:on
十三、破坏性变更与迁移指南
13.1 关键破坏性变更
1. JIT 默认关闭
-- PG18 及之前:jit = on(默认)
-- PG19:jit = off(默认)
-- 影响:OLAP 工作负载可能变慢
-- 建议:升级前在测试环境对比性能
-- 如需恢复 JIT:
ALTER SYSTEM SET jit = on;
2. max_locks_per_transaction 默认值翻倍
-- PG18:max_locks_per_transaction = 64
-- PG19:max_locks_per_transaction = 128
-- 原因:锁分配机制变化,有效容量需要翻倍才能匹配
3. MD5 密码认证警告
-- PG18 标记 MD5 为 deprecated
-- PG19 成功认证后发出警告
-- 建议:迁移到 SCRAM-SHA-256
-- 查看使用 MD5 的角色
SELECT rolname, rolpassword
FROM pg_authid
WHERE rolpassword LIKE 'md5%';
-- 迁移密码
ALTER USER myuser WITH PASSWORD 'new_password';
-- 自动使用 SCRAM-SHA-256
4. RADIUS 支持移除
-- RADIUS(UDP)因安全性原因被移除
-- 替代方案:使用 GSSAPI、SCRAM 或 LDAP
5. inet/cidr 索引 opclass 变更
-- btree_gist 的 inet/cidr opclass 被替换为 GiST 原生 opclass
-- pg_upgrade 如果检测到旧 opclass 会失败
-- 检查是否存在受影响的索引
SELECT indexname, indexdef
FROM pg_indexes
WHERE indexdef LIKE '%btree_gist%' AND indexdef LIKE '%inet%';
13.2 升级路径
# 方法 1:pg_upgrade(快速,停机短)
pg_upgrade -b /usr/lib/postgresql/18/bin -B /usr/lib/postgresql/19/bin \
-d /var/lib/postgresql/18/main -D /var/lib/postgresql/19/main \
--link # 硬链接模式,更快
# 方法 2:逻辑复制(零停机)
# 1. 创建 PG19 实例
# 2. 设置从 PG18 到 PG19 的逻辑复制
# 3. 等待数据同步
# 4. 切换应用连接
# 5. 停止旧实例
# 方法 3:pg_dumpall(最安全,最慢)
pg_dumpall -h old_host -p 5432 | psql -h new_host -p 5432
13.3 升级前检查清单
-- 1. 检查 MD5 密码
SELECT rolname FROM pg_authid WHERE rolpassword LIKE 'md5%';
-- 2. 检查 RADIUS 认证
SELECT * FROM pg_hba_conf WHERE auth_method = 'radius';
-- 3. 检查 btree_gist inet 索引
SELECT indexname FROM pg_indexes WHERE indexdef LIKE '%btree_gist%inet%';
-- 4. 检查 JIT 使用情况
SELECT name, setting FROM pg_settings WHERE name = 'jit';
-- 5. 检查 MULE_INTERNAL 编码
SELECT datname, encoding FROM pg_database
WHERE encoding = (SELECT oid FROM pg_encoding WHERE name = 'MULE_INTERNAL');
-- 6. 检查数据库名/角色名中的换行符
SELECT datname FROM pg_database WHERE datname ~ E'[\r\n]';
SELECT rolname FROM pg_authid WHERE rolname ~ E'[\r\n]';
十四、性能基准测试建议
14.1 使用 pgbench 测试
# 初始化测试数据
pgbench -i -s 100 mydb
# 运行基准测试(PG18 vs PG19)
pgbench -c 10 -j 4 -T 60 -P 5 mydb
# 测试 COPY 性能(SIMD 加速)
time pgbench -f copy_test.sql -c 4 -T 30 mydb
# 测试图查询性能
pgbench -f graph_query.sql -c 4 -T 30 mydb
14.2 关注指标
-- 1. Autovacuum 并行效果
SELECT relname, n_dead_tup, last_autovacuum, last_autoanalyze
FROM pg_stat_user_tables
WHERE n_dead_tup > 10000;
-- 2. 排序性能
-- 对比 EXPLAIN ANALYZE 中的 Sort 节点时间
-- 3. 索引清理时间
-- 对比 autovacuum 日志中的 index vacuum 阶段时间
-- 4. COPY 导入时间
-- 对比百万行数据的导入时间
十五、总结与展望
PostgreSQL 19 是一个务实主义的版本。图查询虽然是最"性感"的特性,但真正影响日常运维的是那些看似平凡的改进:
| 特性 | 影响范围 | 实用等级 |
|---|---|---|
| REPACK CONCURRENTLY | 所有 DBA | ⭐⭐⭐⭐⭐ |
| 在线校验和 | 所有生产环境 | ⭐⭐⭐⭐⭐ |
| 并行 autovacuum | 大表多索引 | ⭐⭐⭐⭐⭐ |
| SIMD COPY | 数据仓库/ETL | ⭐⭐⭐⭐ |
| Radix Sort | OLAP 查询 | ⭐⭐⭐⭐ |
| SQL/PGQ | 社交/推荐系统 | ⭐⭐⭐⭐ |
| FOR PORTION OF | 时态数据场景 | ⭐⭐⭐⭐ |
| ON CONFLICT DO SELECT | 高并发系统 | ⭐⭐⭐⭐ |
| 分区 MERGE/SPLIT | 分区表管理 | ⭐⭐⭐⭐ |
| 逻辑复制增强 | 分布式部署 | ⭐⭐⭐⭐ |
| JIT 默认关闭 | OLTP 系统 | ⭐⭐⭐ |
| GROUP BY ALL | 日常开发 | ⭐⭐⭐ |
我的建议:
- 不要在 GA(正式发布)之前升级生产环境。Beta 阶段适合在测试环境全面测试
- 优先测试 REPACK CONCURRENTLY。如果你维护 GB 级以上的表,这是最直接的收益
- 评估 JIT 关闭对 OLAP 工作负载的影响。在测试环境对比关键报表的执行时间
- 规划 MD5 → SCRAM-SHA-256 的密码迁移
- 启用在线校验和。这是保护数据完整性的零成本保险
- 评估 SQL/PGQ 是否能替代独立的图数据库。对于中小规模图查询,PG19 可能完全够用
PostgreSQL 19 预计将在 2026 年底正式发布(GA)。在此之前,社区鼓励所有用户在测试环境运行 Beta 版本,发现并报告 Bug。三十年而立,PostgreSQL 正在用最务实的方式,回答一个关键问题:在 AI 时代,关系数据库还能做什么?
答案显然是:比你想象的更多。