编程 PostgreSQL 19 深度实战:当三十年老牌数据库迎来图查询革命——从 SQL/PGQ 到 REPACK 并行重组、在线校验和与生产级迁移的完全指南(2026)

2026-06-18 06:23:10 +0800 CST views 132

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 的核心特性进行深度、实战导向的全面解析:

  1. SQL/PGQ 图查询:SQL:2023 标准的原生图查询支持,告别独立图数据库
  2. 时态数据操作:UPDATE/DELETE FOR PORTION OF 的完整实战
  3. ON CONFLICT DO SELECT:原子性"获取或创建"模式
  4. REPACK 命令:替代 VACUUM FULL 和 CLUSTER 的在线表重组
  5. 并行自动清理:autovacuum 的性能飞跃
  6. 在线校验和:零停机数据完整性保障
  7. SIMD 加速与 Radix Sort:底层数据处理性能突破
  8. 逻辑复制增强:序列同步、分区排除、WAIT FOR LSN
  9. 优化器改进:反连接转换、预聚合下推、常量折叠增强
  10. 破坏性变更与迁移指南:从 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 的核心思想是无锁重建

  1. 创建临时表,结构与原表相同
  2. 在临时表上建立与原表相同的索引
  3. 通过触发器同步增量变更
  4. 将原表数据批量复制到临时表
  5. 应用增量变更到临时表
  6. 短暂获取 ACCESS EXCLUSIVE 锁,交换表名
  7. 删除旧表
-- 生产环境的 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 对比

特性VACUUMVACUUM FULLCLUSTERREPACKREPACK CONCURRENTLY
锁级别共享锁ACCESS EXCLUSIVEACCESS EXCLUSIVEACCESS 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 SortOLAP 查询⭐⭐⭐⭐
SQL/PGQ社交/推荐系统⭐⭐⭐⭐
FOR PORTION OF时态数据场景⭐⭐⭐⭐
ON CONFLICT DO SELECT高并发系统⭐⭐⭐⭐
分区 MERGE/SPLIT分区表管理⭐⭐⭐⭐
逻辑复制增强分布式部署⭐⭐⭐⭐
JIT 默认关闭OLTP 系统⭐⭐⭐
GROUP BY ALL日常开发⭐⭐⭐

我的建议

  1. 不要在 GA(正式发布)之前升级生产环境。Beta 阶段适合在测试环境全面测试
  2. 优先测试 REPACK CONCURRENTLY。如果你维护 GB 级以上的表,这是最直接的收益
  3. 评估 JIT 关闭对 OLAP 工作负载的影响。在测试环境对比关键报表的执行时间
  4. 规划 MD5 → SCRAM-SHA-256 的密码迁移
  5. 启用在线校验和。这是保护数据完整性的零成本保险
  6. 评估 SQL/PGQ 是否能替代独立的图数据库。对于中小规模图查询,PG19 可能完全够用

PostgreSQL 19 预计将在 2026 年底正式发布(GA)。在此之前,社区鼓励所有用户在测试环境运行 Beta 版本,发现并报告 Bug。三十年而立,PostgreSQL 正在用最务实的方式,回答一个关键问题:在 AI 时代,关系数据库还能做什么?

答案显然是:比你想象的更多。

复制全文 生成海报 PostgreSQL SQL 数据库 PG19 开源

推荐文章

使用 Vue3 和 Axios 实现 CRUD 操作
2024-11-19 01:57:50 +0800 CST
Linux查看系统配置常用命令
2024-11-17 18:20:42 +0800 CST
css模拟了MacBook的外观
2024-11-18 14:07:40 +0800 CST
go发送邮件代码
2024-11-18 18:30:31 +0800 CST
介绍25个常用的正则表达式
2024-11-18 12:43:00 +0800 CST
Vue3中如何处理异步操作?
2024-11-19 04:06:07 +0800 CST
Roop是一款免费开源的AI换脸工具
2024-11-19 08:31:01 +0800 CST
Vue3中的Store模式有哪些改进?
2024-11-18 11:47:53 +0800 CST
php获取当前域名
2024-11-18 00:12:48 +0800 CST
维护网站维护费一年多少钱?
2024-11-19 08:05:52 +0800 CST
详解 Nginx 的 `sub_filter` 指令
2024-11-19 02:09:49 +0800 CST
企业官网案例-芊诺网络科技官网
2024-11-18 11:30:20 +0800 CST
程序员茄子在线接单