编程 PostgreSQL 19 Beta 1 深度解析:当「全球最强开源数据库」学会了图查询——从 SQL/PGQ 标准实现到生产级性能优化的完全指南(2026)

2026-06-09 07:46:24 +0800 CST views 36

PostgreSQL 19 Beta 1 深度解析:当「全球最强开源数据库」学会了图查询——从 SQL/PGQ 标准实现到生产级性能优化的完全指南(2026)

引言:PostgreSQL 19 来了,这次真的不一样

2026 年 6 月 4 日,PostgreSQL 全球开发组正式发布了 PostgreSQL 19 Beta 1。作为全球最先进的开源关系型数据库,PostgreSQL 的每一次大版本更新都牵动着无数 DBA 和开发者的神经。而 PG19 这次带来的,绝不仅仅是一堆增量修补——SQL/PGQ 图查询标准的首个完整实现,意味着 PostgreSQL 在不改变存储引擎的前提下,正式进入了图计算领域。

但这远不是 PG19 的全部。从 SIMD 加速的 COPY 导入、自动并行 vacuum、在线启用数据校验和、到异步 I/O 读取调度优化,PG19 在性能层面的改进几乎触及了数据库的每一个核心路径。

本文将从以下维度全面解析 PostgreSQL 19 Beta 1:

  1. SQL/PGQ 图查询——ISO 标准实现,用 SQL 写图查询,告别 Neo4j
  2. 查询优化器革命——反连接优化、半连接改进、聚合下推
  3. 性能全面提速——SIMD、异步 I/O、并行 vacuum、Radix Sort
  4. 存储与压缩——默认 LZ4 压缩、TOAST 改进
  5. 运维与可观测性——在线校验和、锁定统计、自动 vacuum 评分系统
  6. 兼容性变更与迁移指南——破坏性改动一览

一、SQL/PGQ:在关系表上做图查询,这不是玩笑

1.1 什么是 SQL/PGQ?

SQL/PGQ(Property Graph Queries)是 ISO/IEC 9075-16:2023 标准的一部分,它定义了一种在 SQL 中嵌入图模式匹配语法的方式。简单来说,你可以在已有的关系表上声明一个「属性图」,然后用类似 Cypher 的 MATCH 语法来查询它。

在 PG19 之前,如果你想在关系数据库中做图查询,你需要:

  • 写出极其复杂的多表自连接(Self-Join)
  • 用递归 CTE(Common Table Expressions)来做路径搜索
  • 结果 SQL 又臭又长,维护成本极高

而 SQL/PGQ 让这一切变得优雅。

1.2 核心概念:属性图

属性图由两部分组成:

  • 顶点(Vertex):代表一个实体,如用户、产品、账户
  • 边(Edge):代表实体之间的关系,如好友、购买、关注

两者都可以携带属性。例如一个 Person 顶点可以有 namebirth_year 属性,一条 Knows 边可以有 since(认识时间)属性。

1.3 声明属性图

PG19 引入了 CREATE PROPERTY GRAPH 语法,将现有关系表映射为图的顶点和边:

-- 假设我们有两个表:people 和 friendships

-- people 表(顶点)
CREATE TABLE people (
    id          SERIAL PRIMARY KEY,
    name        TEXT NOT NULL,
    birth_year  INT,
    email       TEXT UNIQUE
);

-- friendships 表(边)
CREATE TABLE friendships (
    person_a    INT REFERENCES people(id),
    person_b    INT REFERENCES people(id),
    since       DATE,
    closeness   NUMERIC(1,1) CHECK (closeness >= 0 AND closeness <= 1)
);

-- 声明属性图
CREATE PROPERTY GRAPH social_graph
VERTEX TABLES (
    people LABEL Person PROPERTIES (id, name, birth_year, email)
)
EDGE TABLES (
    friendships
        SOURCE KEY (person_a) REFERENCES people (id)
        DESTINATION KEY (person_b) REFERENCES people (id)
        LABEL Knows PROPERTIES (since, closeness)
);

注意几个关键点:

  • LABEL 给顶点/边起了一个语义名称,后续 MATCH 语法中使用
  • PROPERTIES 指定哪些列作为图的属性暴露出来
  • SOURCE KEYDESTINATION KEY 定义了边的方向性

1.4 图模式匹配查询

声明完属性图后,你就可以用 GRAPH_TABLE 函数做图查询了:

-- 基础查询:找 Jan 的所有朋友
SELECT friend_name
FROM GRAPH_TABLE (
    social_graph
    MATCH (a:Person WHERE a.name = 'Jan')-[k:Knows]->(b:Person)
    COLUMNS (b.name AS friend_name)
);

-- 两跳查询:找 Jan 的朋友的朋友(朋友的朋友)
SELECT friend_of_friend
FROM GRAPH_TABLE (
    social_graph
    MATCH (a:Person WHERE a.name = 'Jan')-[k1:Knows]->(b:Person)-[k2:Knows]->(c:Person)
    COLUMNS (c.name AS friend_of_friend)
);

-- 带属性过滤:找 Jan 在 2024 年以后认识的朋友
SELECT friend_name, since_date
FROM GRAPH_TABLE (
    social_graph
    MATCH (a:Person WHERE a.name = 'Jan')-[k:Knows WHERE k.since > '2024-01-01']->(b:Person)
    COLUMNS (b.name AS friend_name, k.since AS since_date)
);

-- 无方向边:双向朋友关系
SELECT mutual_friend
FROM GRAPH_TABLE (
    social_graph
    MATCH (a:Person WHERE a.name = 'Jan')-[k:Knows]-(b:Person)
    COLUMNS (b.name AS mutual_friend)
);

MATCH 子句的语法解析:

  • (a:Person) —— 匹配一个标签为 Person 的顶点,绑定到变量 a
  • WHERE a.name = 'Jan' —— 顶点过滤器
  • -[k:Knows]-> —— 匹配一条标签为 Knows 的出边
  • -[k:Knows]-(b:Person) —— 无方向匹配(匹配入边或出边)
  • COLUMNS (...) —— 投影你想要的输出列

1.5 实战场景:社交网络推荐系统

让我们构建一个更完整的例子——社交网络的好友推荐引擎:

-- 创建更丰富的数据模型
CREATE TABLE users (
    id          BIGSERIAL PRIMARY KEY,
    username    VARCHAR(50) NOT NULL UNIQUE,
    display_name TEXT NOT NULL,
    created_at  TIMESTAMPTZ DEFAULT NOW(),
    is_active   BOOLEAN DEFAULT true
);

CREATE TABLE follows (
    follower_id   BIGINT REFERENCES users(id),
    following_id  BIGINT REFERENCES users(id),
    created_at    TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (follower_id, following_id)
);

CREATE TABLE user_interests (
    user_id      BIGINT REFERENCES users(id),
    interest_tag  TEXT NOT NULL,
    weight        NUMERIC(1,3) DEFAULT 1.0,
    PRIMARY KEY (user_id, interest_tag)
);

-- 声明属性图
CREATE PROPERTY GRAPH recommendation_graph
VERTEX TABLES (
    users LABEL User PROPERTIES (id, username, display_name, created_at, is_active)
)
EDGE TABLES (
    follows
        SOURCE KEY (follower_id) REFERENCES users (id)
        DESTINATION KEY (following_id) REFERENCES users (id)
        LABEL Follows PROPERTIES (created_at)
);

-- 推荐「关注的人也关注的人」
SELECT DISTINCT recommended
FROM GRAPH_TABLE (
    recommendation_graph
    MATCH (me:User WHERE me.username = 'current_user')
          -[f1:Follows]->(my_following:User)
          -[f2:Follows]->(fof:User)
    WHERE fof <> me
    COLUMNS (fof.username AS recommended)
)
ORDER BY recommended
LIMIT 20;

-- 与 common interest 的用户建立连接
SELECT candidate
FROM GRAPH_TABLE (
    recommendation_graph
    MATCH (me:User WHERE me.username = 'current_user')
          -[f:Follows]->(mutual_follow:User)
          <-[f2:Follows]-(candidate:User)
    WHERE candidate <> me
    COLUMNS (candidate.username AS candidate)
)
ORDER BY candidate
LIMIT 10;

1.6 底层实现原理:重写器,而非图引擎

这里有一个非常重要的设计决策需要理解:PG19 的 SQL/PGQ 实现不是一个图存储引擎,而是一个查询重写器。

当你执行 GRAPH_TABLE 查询时,PG19 内部的处理流程如下:

GRAPH_TABLE 查询
    ↓
图模式解析器(解析 MATCH 语法)
    ↓
图重写器(将图模式转换为关系代数)
    ↓
标准查询规划器(已有的优化器)
    ↓
执行器(已有的执行引擎)

MATCH (a:Person)-[k:Knows]->(b:Person) 实际上被重写为:

-- 概念上等价于:
SELECT b.*
FROM people a
JOIN friendships k ON k.person_a = a.id
JOIN people b ON b.id = k.person_b
WHERE a.name = 'Jan';

这个设计带来了两个重大影响:

第一:你现有的索引全部有效。

因为重写器生成的是标准的关系连接(JOIN),所以你在 friendships(person_a) 上建的 B-tree 索引会被正常使用。不需要维护独立的图索引,不需要额外的存储开销。

第二:性能特征与关系查询一致。

图遍历变成 JOIN 链。对于浅层、固定深度的查询(1-3 跳),性能非常优秀——因为 PG 本来就擅长索引连接。但对于深层、可变长度的遍历(如六度分隔),性能可能不如专用图数据库如 Neo4j。

1.7 PG19 的 SQL/PGQ 局限性

作为首个实现,PG19 的 SQL/PGQ 有一些需要注意的限制:

  1. 不支持可变长度路径-[k:Knows*1..5]->(最多 5 跳)尚未支持,这是后续版本的重点
  2. 不支持量化模式:如 MATCH SHORTESTALL SHORTEST 等路径算法
  3. 性能深度有限:对于超过 3 跳的深层遍历,性能特征等同于等价的多表 JOIN

对于大多数业务场景(2-3 跳的朋友查询、权限路径审计、数据血缘追踪),PG19 的 SQL/PGQ 已经完全够用了。


二、查询优化器的全面升级

PG19 在查询优化器方面做了大量改进,有些改动直接影响你写的每一行 SQL。

2.1 NOT IN → 反连接优化

NOT IN 子查询一直是 PostgreSQL 性能优化的老大难问题。当子查询中存在 NULL 值时,NOT IN 无法被优化为反连接(Anti-Join),导致全表扫描。

PG19 中,优化器现在能够在检测到 NULL 不存在时,将 NOT IN 转换为更高效的 ANTI JOIN

-- 优化前:如果 status 中有 NULL,会退化为全表扫描
SELECT * FROM orders
WHERE customer_id NOT IN (
    SELECT id FROM customers WHERE status = 'inactive'
);

-- PG19 优化后:当优化器能证明 NULL 不存在时
-- 自动转换为 Anti-Join,走索引
EXPLAIN ANALYZE
SELECT * FROM orders o
WHERE NOT EXISTS (
    SELECT 1 FROM customers c
    WHERE c.id = o.customer_id AND c.status = 'inactive'
);

2.2 更多 LEFT JOIN → Anti-Join 转换

PG19 扩大了 LEFT JOINANTI JOIN 的转换范围。当外连接的结果不包含 NULL 行(即无法匹配的情况没有副作用),优化器可以将 LEFT JOIN 替换为 ANTI JOIN,大幅减少中间结果集。

-- 经典场景:查找没有下过订单的客户
SELECT c.name, c.email
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;

-- PG19 可能将其转换为:
SELECT c.name, c.email
FROM customers c
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.customer_id = c.id
);

2.3 Memoize 用于 Anti-Join

Memoize 是 PostgreSQL 的一个内置操作符,用于缓存子查询的结果。PG19 现在允许 Memoize 用于内侧唯一的反连接,这在关联子查询场景下可以显著减少重复计算。

2.4 聚合下推到 JOIN 之前

这是一个非常有深度的优化。某些聚合操作现在可以在 JOIN 之前执行,从而减少 JOIN 需要处理的行数:

-- 示例:查找每个部门工资最高的人
-- PG19 可能在 JOIN 前先完成 GROUP BY,减少 JOIN 数据量
SELECT d.dept_name, e.name, e.salary
FROM (
    SELECT dept_id, MAX(salary) AS max_salary
    FROM employees
    GROUP BY dept_id
) dept_max
JOIN employees e ON e.dept_id = dept_max.dept_id AND e.salary = dept_max.max_salary
JOIN departments d ON d.id = e.dept_id;

2.5 其他优化器改进

优化项说明
IS NOT DISTINCT FROM NULLIS NOT NULL常量折叠简化
Var IS NULL 早期评估让下游优化器利用更多信息
Append/MergeAppend 支持增量排序分区表查询受益
Hash Join NULL 键处理改进避免不必要的数据倾斜
半连接(Semi-Join)规划改进EXISTS 子查询更快
扩展统计支持虚拟生成列对生成列的统计更准确
pg_restore_extended_stats()扩展统计终于可以备份恢复了

三、性能:从 SIMD 到并行 Vacuum 的全面提速

3.1 SIMD 加速 COPY FROM

COPY FROM 是 PostgreSQL 批量导入数据的首选方式。PG19 使用 SIMD(Single Instruction Multiple Data)CPU 指令加速了文本和 CSV 格式的解析。

实测中,对于大字段较多的宽表,COPY FROM 的性能提升可达 20-40%

-- 传统导入
COPY large_table FROM '/data/import.csv' WITH (FORMAT csv, HEADER true);

-- PG19 中,同样的命令自动使用 SIMD 加速
-- 无需修改任何代码

这对 ETL 流水线和数据迁移场景是实打实的性能红利。

3.2 异步 I/O 读取调度优化

PG19 改进了异步 I/O 的读取预读调度策略,特别是在处理大请求时的表现。新引入的 io_method 配置参数允许 I/O 工作进程自动管理:

-- postgresql.conf 中的新配置
io_min_workers = 2           -- 最小 I/O 工作线程
io_max_workers = 8           -- 最大 I/O 工作线程
io_worker_idle_timeout = 60  -- 空闲超时(秒)
io_worker_launch_interval = 10 -- 工作线程启动间隔(毫秒)

这对 I/O 密集型工作负载(如大表扫描、备份恢复)有显著帮助。

3.3 并行 Vacuum

Autovacuum 终于支持并行工作线程了。在 PG18 及之前,VACUUM 操作是单线程的,对于大表的清理操作可能需要很长时间。

-- 全局配置
autovacuum_max_parallel_workers = 2;

-- 按表配置
ALTER TABLE large_table SET (autovacuum_parallel_workers = 4);

-- 手动并行 vacuum
VACUUM (PARALLEL 4) large_table;

配合 TID Range Scan 的并行化支持,大表的维护操作效率大幅提升。

3.4 Radix Sort(基数排序)

PG19 在排序操作中引入了 Radix Sort(基数排序)算法。Radix Sort 的时间复杂度是 O(n×k),其中 k 是键的最大位数,对于整数排序特别高效。

-- 整数排序自动使用 Radix Sort
SELECT * FROM events ORDER BY id;

-- 字符串排序也可能受益(取决于数据分布)
SELECT * FROM users ORDER BY created_at;

3.5 默认压缩算法改为 LZ4

PG19 将 TOAST(The Oversized-Attribute Storage Technique)的默认压缩算法从 pglz 改为 lz4。LZ4 在压缩速度和解压速度上都显著优于 pglz,虽然压缩比可能略低,但综合性能更好。

-- 查看当前压缩设置
SHOW default_toast_compression;

-- 手动设置
SET default_toast_compression = 'lz4';

-- 创建表时指定
CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    content TEXT COMPRESSION lz4
);

3.6 NOTIFY 性能优化

NOTIFY 命令现在只会唤醒正在监听特定通道的后端进程,而不是唤醒所有后端进程。对于使用 LISTEN/NOTIFY 做实时通知的应用(如 WebSocket 推送),在高并发场景下性能提升显著。

-- 发送端
NOTIFY 'order_updates', '新订单 #' || NEW.id;

-- 接收端(仅相关监听者被唤醒)
LISTEN order_updates;

3.7 其他性能改进

改进项影响
外键约束检查性能提升高并发写入场景受益
表扫描标记 all-visible 页面减少后续 VACUUM 的工作量
UTF-8 大小写折叠加速LC_COLLATE 相关操作更快
Hash 索引批量删除优化大表维护效率提升
GIN 索引 VACUUM 流式读取并行 vacuum 场景更高效
行解变形(Row Deformation)优化内部数据格式转换更快
JIT 默认关闭减少 OLTP 场景下的 JIT 编译开销

四、存储与压缩:LZ4 成为默认选择

4.1 TOAST 压缩机制回顾

PostgreSQL 使用 TOAST 机制存储超过页面大小(通常 8KB)的大字段。当一行数据无法放入单个数据页时,PG 会将大字段压缩并存储到 TOAST 表中。

压缩算法的选择直接影响写入和读取性能:

算法压缩速度解压速度压缩比
pglz(PG 传统)中等中等较高
lz4(PG19 默认)极快极快中等
zstd(可选扩展)最高

4.2 实际性能对比

-- 测试不同压缩算法的效果
CREATE TABLE toast_test_pglz (
    id SERIAL,
    data TEXT COMPRESSION pglz
);

CREATE TABLE toast_test_lz4 (
    id SERIAL,
    data TEXT COMPRESSION lz4
);

-- 插入 100 万条测试数据
INSERT INTO toast_test_pglz (data)
SELECT repeat(md5(random()::text), 100) FROM generate_series(1, 1000000);

INSERT INTO toast_test_lz4 (data)
SELECT repeat(md5(random()::text), 100) FROM generate_series(1, 1000000);

-- 查看表大小差异
SELECT pg_size_pretty(pg_table_size('toast_test_pglz')) AS pglz_size,
       pg_size_pretty(pg_table_size('toast_test_lz4')) AS lz4_size;

对于大多数 OLTP 场景,LZ4 是更好的选择——因为压缩和解压速度更快,CPU 开销更低,而压缩比的差异通常可以接受。

4.3 迁移建议

升级到 PG19 后,现有数据的压缩格式不会自动改变。只有新写入的数据会使用 LZ4。如果你想让现有数据也使用 LZ4:

-- 方法 1:逐表重写(需要停机)
VACUUM FULL table_name;

-- 方法 2:使用逻辑复制
-- 在新集群上重建,数据会自动使用 LZ4 压缩

五、运维与可观测性:DBA 的福音

5.1 在线启用/禁用数据校验和

这是 PG19 最实用的运维特性之一。在 PG18 及之前,数据校验和只能在初始化时设置,修改需要停机并使用 pg_checksums 工具。

PG19 终于支持在线操作:

-- 在线启用数据校验和(无需停机!)
ALTER SYSTEM SET data_checksums = on;
SELECT pg_catalog.pg_enable_data_checksums();

-- 在线禁用
ALTER SYSTEM SET data_checksums = off;
SELECT pg_catalog.pg_disable_data_checksums();

数据校验和能够检测静默数据损坏(如磁盘位翻转),对于金融、医疗等对数据完整性要求极高的场景至关重要。

5.2 自动 Vacuum 评分系统

PG19 引入了一个新的评分系统来控制自动 vacuum 的调度优先级:

-- 新的配置参数
autovacuum_freeze_score_weight = 10.0
autovacuum_multixact_freeze_score_weight = 10.0
autovacuum_vacuum_score_weight = 5.0
autovacuum_analyze_score_weight = 3.0
autovacuum_insert_score_weight = 2.0

-- 监控自动 vacuum 评分
SELECT * FROM pg_stat_autovacuum_scores;

这套评分系统让你能更精细地控制哪些表优先被 vacuum 处理,而不是依赖简单的阈值触发。

5.3 锁定统计视图

新增的 pg_stat_lock 视图提供了按锁类型分类的统计信息:

-- 查看锁定统计
SELECT locktype, mode, granted, count, wait_time
FROM pg_stat_lock
ORDER BY wait_time DESC;

这对排查锁竞争问题非常有用——你可以快速定位哪些锁类型导致了最多的等待。

5.4 恢复状态监控

-- 新视图:查看恢复状态
SELECT * FROM pg_stat_recovery;

对于使用流复制或 PITR 的环境,这个视图提供了恢复进度的详细信息。

5.5 其他监控改进

-- WAL 接收器状态增强(新增 connecting 状态)
SELECT status, sender_host, sender_port FROM pg_stat_wal_receiver;

-- Full Page Write 字节数统计
SELECT wal_fpw_records, wal_fpw_bytes FROM pg_stat_wal;

-- 序列页面 LSN 追踪
SELECT pg_get_sequence_data('my_sequence');

-- MultiXact 统计
SELECT * FROM pg_get_multixact_stats();

-- 动态共享内存详情
SELECT * FROM pg_dsm_registry_allocations;

-- 扩展统计备份恢复
SELECT pg_restore_extended_stats('my_stats_obj');

5.6 日志增强

-- 按进程类型设置不同日志级别
log_min_messages = 'autovacuum:warning, bgwriter:debug5'

-- 记录长时间自动分析操作
log_autoanalyze_min_duration = 1000  -- 毫秒

-- 锁等待日志默认启用
log_lock_waits = on  -- 默认已开启!

-- 裸解析树日志(调试用)
debug_print_parse = on

六、兼容性变更与迁移指南

PG19 有一些破坏性变更,升级前务必检查。

6.1 关键破坏性变更

6.1.1 RADIUS 认证被移除

PostgreSQL 只支持基于 UDP 的 RADIUS,而基于 UDP 的 RADIUS 被认为是不安全的。如果你的集群使用 RADIUS 认证,升级前需要切换到其他认证方式(如 LDAP、GSSAPI、SCRAM-SHA-256)。

6.1.2 standard_conforming_strings 强制开启

-- PG18 及以下:可以关闭(允许 E'...' 转义)
SET standard_conforming_strings = off;

-- PG19:强制开启,无法关闭
-- 如果你依赖旧式转义字符串,需要修改应用代码

escape_string_warning 配置参数也被移除。

6.1.3 inet/cidr 默认操作类变更

inetcidr 类型的默认索引操作类从 B-tree 改为 GiST。如果你的旧集群使用了 btree_gist 扩展的 inet/cidr 操作类,pg_upgrade 会失败。

-- 升级前检查
SELECT indexname, indexdef
FROM pg_indexes
WHERE indexdef LIKE '%btree_gist%' AND indexdef LIKE '%inet%';

-- 如果有匹配结果,需要先删除这些索引
DROP INDEX old_inet_index;
-- 然后使用标准 GiST 索引重建
CREATE INDEX new_inet_index ON table USING gist (inet_column);

6.1.4 max_locks_per_transaction 默认值从 64 改为 128

锁空间分配机制发生了变化,实际容量需要翻倍才能匹配之前的容量。对于大部分场景,默认值是足够的,但如果你自定义了这个参数,需要注意调整。

6.1.5 JIT 默认关闭

PG19 将 JIT 编译默认关闭,因为之前的成本估算被认为不够可靠。如果你依赖 JIT 做复杂分析查询,需要手动启用:

SET jit = on;

6.1.6 MULE_INTERNAL 编码被移除

这个编码复杂且几乎不被使用。如果你的数据库使用了这个编码,需要先导出并转换为其他编码。

6.1.7 其他变更

  • 数据库名、角色名、表空间名中禁止使用回车符和换行符
  • json_array() 无行时返回空数组(而非 NULL)
  • COPY FROM ... WHERE 中禁用系统列
  • CREATE SCHEMA 中不再自动重排序非 schema 对象
  • MD5 密码认证成功后会产生警告(PG18 已弃用 MD5 密码)
  • 新增 password_expiration_warning_threshold(默认 7 天)

6.2 升级检查清单

# 1. 检查 RADIUS 认证使用
grep -r "radius" $PGDATA/pg_hba.conf

# 2. 检查 btree_gist inet/cidr 索引
psql -c "SELECT indexname FROM pg_indexes WHERE indexdef ~ 'btree_gist' AND indexdef ~ 'inet'"

# 3. 检查非标准字符串使用
psql -c "SELECT datname, datcollate FROM pg_database WHERE NOT datistemplate"

# 4. 检查 MULE_INTERNAL 编码
psql -c "SELECT datname, encoding FROM pg_database WHERE encoding = -9"

# 5. 使用 pg_upgrade 进行测试升级
pg_upgrade --old-bindir /usr/lib/postgresql/18/bin \
          --new-bindir /usr/lib/postgresql/19/bin \
          --old-datadir /var/lib/postgresql/18/main \
          --new-datadir /var/lib/postgresql/19/main \
          --check

七、实战演练:搭建 PG19 并测试 SQL/PGQ

7.1 安装 PG19 Beta 1

# macOS (Homebrew)
brew install postgresql@19-beta

# 或者从源码编译
wget https://ftp.postgresql.org/pub/source/v19beta1/postgresql-19beta1.tar.gz
tar xzf postgresql-19beta1.tar.gz
cd postgresql-19beta1

./configure --prefix=/usr/local/pgsql-19 \
            --with-icu \
            --with-lz4 \
            --with-openssl
make -j$(nproc)
make install

# 初始化数据库
initdb -D /usr/local/pgsql-19/data

# 启动
pg_ctl -D /usr/local/pgsql-19/data -l logfile start

# 启用 LZ4 压缩(postgresql.conf)
echo "default_toast_compression = 'lz4'" >> $PGDATA/postgresql.conf
echo "log_lock_waits = on" >> $PGDATA/postgresql.conf

7.2 完整的 SQL/PGQ 实战案例

-- ========================================
-- 案例企业级系统:供应链追踪
-- ========================================

-- 供应商表(顶点)
CREATE TABLE suppliers (
    id          BIGSERIAL PRIMARY KEY,
    name        TEXT NOT NULL,
    country     TEXT NOT NULL,
    rating      NUMERIC(1,1)
);

-- 产品表(顶点)
CREATE TABLE products (
    id          BIGSERIAL PRIMARY KEY,
    name        TEXT NOT NULL,
    category    TEXT NOT NULL,
    unit_price  NUMERIC(10,2)
);

-- 仓库表(顶点)
CREATE TABLE warehouses (
    id          BIGSERIAL PRIMARY KEY,
    name        TEXT NOT NULL,
    location    TEXT NOT NULL,
    capacity    INT
);

-- 供货关系(边:供应商 → 产品)
CREATE TABLE supplies (
    supplier_id BIGINT REFERENCES suppliers(id),
    product_id  BIGINT REFERENCES products(id),
    unit_cost   NUMERIC(10,2),
    lead_days   INT,
    PRIMARY KEY (supplier_id, product_id)
);

-- 库存关系(边:产品 → 仓库)
CREATE TABLE stocked_in (
    product_id    BIGINT REFERENCES products(id),
    warehouse_id  BIGINT REFERENCES warehouses(id),
    quantity      INT NOT NULL DEFAULT 0,
    reorder_level INT DEFAULT 100,
    PRIMARY KEY (product_id, warehouse_id)
);

-- 订单关系(边:仓库 → 仓库,表示调拨)
CREATE TABLE transfers (
    from_warehouse_id BIGINT REFERENCES warehouses(id),
    to_warehouse_id   BIGINT REFERENCES warehouses(id),
    product_id        BIGINT REFERENCES products(id),
    quantity         INT NOT NULL,
    transfer_date    DATE DEFAULT CURRENT_DATE,
    PRIMARY KEY (from_warehouse_id, to_warehouse_id, product_id, transfer_date)
);

-- 声明供应链属性图
CREATE PROPERTY GRAPH supply_chain
VERTEX TABLES (
    suppliers  LABEL Supplier  PROPERTIES (id, name, country, rating),
    products   LABEL Product   PROPERTIES (id, name, category, unit_price),
    warehouses LABEL Warehouse PROPERTIES (id, name, location, capacity)
)
EDGE TABLES (
    supplies
        SOURCE KEY (supplier_id) REFERENCES suppliers (id)
        DESTINATION KEY (product_id) REFERENCES products (id)
        LABEL Supplies PROPERTIES (unit_cost, lead_days),
    stocked_in
        SOURCE KEY (product_id) REFERENCES products (id)
        DESTINATION KEY (warehouse_id) REFERENCES warehouses (id)
        LABEL StockedIn PROPERTIES (quantity, reorder_level),
    transfers
        SOURCE KEY (from_warehouse_id) REFERENCES warehouses (id)
        DESTINATION KEY (to_warehouse_id) REFERENCES warehouses (id)
        LABEL Transfers PROPERTIES (quantity, transfer_date)
);

-- 查询 1:找某产品的所有供应商
SELECT supplier_name, unit_cost, lead_days
FROM GRAPH_TABLE (
    supply_chain
    MATCH (s:Supplier)-[sup:Supplies]->(p:Product WHERE p.name = '高精度传感器')
    COLUMNS (s.name AS supplier_name, sup.unit_cost, sup.lead_days)
)
ORDER BY unit_cost;

-- 查询 2:找某供应商提供的所有产品,以及这些产品在哪些仓库有库存
SELECT product_name, warehouse_name, quantity
FROM GRAPH_TABLE (
    supply_chain
    MATCH (s:Supplier WHERE s.name = '深圳精密电子')
          -[sup:Supplies]->(p:Product)
          -[st:StockedIn]->(w:Warehouse)
    COLUMNS (p.name AS product_name, w.name AS warehouse_name, st.quantity)
)
ORDER BY product_name, quantity DESC;

-- 查询 3:找从供应商到仓库的完整路径
SELECT supplier_name, product_name, warehouse_name, lead_days, stock_qty
FROM GRAPH_TABLE (
    supply_chain
    MATCH (s:Supplier WHERE s.country = '中国')
          -[sup:Supplies]->(p:Product WHERE p.category = '电子元件')
          -[st:StockedIn]->(w:Warehouse WHERE w.location LIKE '华东%')
    COLUMNS (
        s.name AS supplier_name,
        p.name AS product_name,
        w.name AS warehouse_name,
        sup.lead_days,
        st.quantity AS stock_qty
    )
)
WHERE stock_qty > 0
ORDER BY lead_days;

-- 查询 4:找有过调拨关系的仓库对
SELECT from_wh, to_wh, product_name, total_qty
FROM GRAPH_TABLE (
    supply_chain
    MATCH (w1:Warehouse)-[t:Transfers]->(w2:Warehouse)
    COLUMNS (
        w1.name AS from_wh,
        w2.name AS to_wh,
        t.product_id,
        t.quantity AS total_qty
    )
) gt
JOIN products p ON p.id = gt.product_id
ORDER BY total_qty DESC;

7.3 性能测试:SQL/PGQ vs 等价 JOIN

-- SQL/PGQ 方式
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT friend_name
FROM GRAPH_TABLE (
    social_graph
    MATCH (a:Person WHERE a.name = 'Jan')-[k:Knows]->(b:Person)
    COLUMNS (b.name AS friend_name)
);

-- 等价 JOIN 方式
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT b.name AS friend_name
FROM people a
JOIN friendships k ON k.person_a = a.id
JOIN people b ON b.id = k.person_b
WHERE a.name = 'Jan';

-- 你会发现执行计划几乎完全一致
-- 这就是重写器的力量——语法糖,但不是黑盒

八、总结展望:PostgreSQL 的进化之路

8.1 PG19 的战略意义

PostgreSQL 19 的发布标志着几个重要趋势:

  1. 图查询成为标准特性:SQL/PGQ 的实现意味着图查询不再是专用数据库的专利。对于大多数"轻度"图查询需求,你现在可以留在 PostgreSQL 生态中完成,而不需要引入 Neo4j、JanusGraph 等额外的技术栈。

  2. 性能优化的系统化:从 SIMD 到 Radix Sort,从并行 Vacuum 到异步 I/O 优化,PG19 的性能改进覆盖了数据库的核心路径。这不是某个单一特性的突破,而是整体工程质量的提升。

  3. 运维友好度持续提升:在线校验和、锁定统计视图、vacuum 评分系统——这些看似"无趣"的特性,对于 7×24 运行的生产系统来说,恰恰是最有价值的。

8.2 SQL/PGQ 的未来

PG19 是 SQL/PGQ 的首个实现,未来可以期待:

  • 可变长度路径支持-[k:Knows*1..5]-> 语法
  • 路径算法:最短路径、全最短路径
  • 图算法扩展:PageRank、社区发现、中心度计算
  • 更好的图专用优化:为图遍历模式定制化的执行策略

8.3 对开发者的建议

  1. Beta 阶段不要用于生产:PG19 目前还是 Beta 1,API 和行为可能变化。在测试环境充分验证后再考虑升级。
  2. 评估 SQL/PGQ 的适用性:如果你的业务有图查询需求(社交网络、供应链、权限模型),评估是否可以在 PG19 中统一解决。
  3. 关注性能基线:LZ4 默认压缩、并行 vacuum、JIT 默认关闭——这些改动都会影响你的性能特征,升级后建议重新进行基准测试。
  4. 提前检查兼容性:特别是 inet/cidr 索引操作类变更和 RADIUS 认证移除。

PostgreSQL 正在从一个"可靠的关系型数据库"进化为一个"全功能数据平台"。SQL/PGQ 的加入不是一个噱头,而是让 PostgreSQL 真正具备了处理多模态数据查询的能力——关系型、文档型、图型,全部在一个引擎中完成。

这是 PostgreSQL 35 年历史中的又一次重要进化。

复制全文 生成海报 PostgreSQL SQL/PGQ 图查询 开源数据库 PG19

推荐文章

php使用文件锁解决少量并发问题
2024-11-17 05:07:57 +0800 CST
pycm:一个强大的混淆矩阵库
2024-11-18 16:17:54 +0800 CST
在 Rust 生产项目中存储数据
2024-11-19 02:35:11 +0800 CST
Vue中如何处理异步更新DOM?
2024-11-18 22:38:53 +0800 CST
PHP如何进行MySQL数据备份?
2024-11-18 20:40:25 +0800 CST
PHP解决XSS攻击
2024-11-19 02:17:37 +0800 CST
程序员茄子在线接单