Ayoub Kazar:将 PostgreSQL 用作图数据库,第 1 部分:PostgreSQL 中的 SQL/PGQ

Ayoub Kazar:将 PostgreSQL 用作图数据库,第 1 部分:PostgreSQL 中的 SQL/PGQ

💡 原文英文,约2300词,阅读约需9分钟。
📝

内容提要

文章介绍PostgreSQL的SQL/PGQ图查询扩展:可在现有表上定义属性图,并用类Cypher模式查询;重写器将图模式转换为普通JOIN和UNION,复用索引、统计与并行查询,无需新执行器。已提交实现仅支持100项标准特性中的26项,缺少量词、路径模式和变长边等关键功能。该功能因目录表示和依赖问题从PostgreSQL 19撤回,预计在20回归;变长边是未来重点。

🔎

延伸解读

重写而非新引擎:SQL/PGQ 的工程取舍

SQL/PGQ 在 PostgreSQL 中没有引入新的执行器节点,而是由重写器将图模式展开为普通 JOIN 和 UNION ALL。这意味着查询计划仍由现有优化器生成,索引、统计信息和并行查询能力可以直接复用。代价是固定长度模式必须连接顶点表,即使查询不请求顶点属性;若在纯 SQL 中自行建模并定义外键,仅遍历边表可能更轻量。

标准覆盖有限:26/100 背后的功能缺口

已提交实现仅覆盖标准 100 项特性中的 26 项,缺少量词、路径模式、路径搜索前缀和匹配模式等关键机制。当前行为相当于默认 WALK 和 REPEATABLE ELEMENTS,且只支持 ALL 路径搜索。这些缺口意味着它尚不能表达变长跳数、无重复边或最短路径等常见图查询需求,读者不宜将其视为完整的图查询方案。

撤回原因与后续影响

该功能因目录表示和对象依赖问题从 PostgreSQL 19 撤回,预计在 20 回归。撤回针对的是目录和依赖跟踪的基础设计,而非重写引擎本身,因此现有重写逻辑预计可保留。后续变长边、多路径模式等工作仍可建立在这一基础上。文章也指出,由于 PostgreSQL 缺少解析器钩子,扩展无法添加语法,SQL/PGQ 只能进入核心或以运行时字符串解析实现。

变长边:最受期待也最难实现

变长边是图查询实践者最常提出的需求,也是选择图查询语言而非手写 JOIN 的常见原因。标准将跳数范围、路径可接受性和返回路径选择分开定义,不同组合对应不同执行策略。文章列举了递归 CTE 重写、按长度展开 UNION ALL 以及执行器级 DFS/BFS 等方案,并指出按长度展开无法处理无界情况。未来实现需权衡可达性、最短路径和枚举所有路径等目标。

❓

Q&A

SQL/PGQ 在 PostgreSQL 中是如何执行图模式查询的?

PostgreSQL 没有新增执行器节点。解析器将 GRAPH_TABLE 转换为新的范围表项 RTE_GRAPH_TABLE,重写器(与展开视图相同的阶段)将其替换为普通子查询。重写过程包括:将模式读入元素模式列表、为每个唯一变量创建路径因子、将标签表达式解析为表、通过递归 DFS 枚举有效组合,每个有效组合变成边表与顶点表之间的 JOIN,所有组合用 UNION ALL 合并。之后规划器和执行器只看到 JOIN 和 UNION,可复用索引、统计信息和并行查询。

PostgreSQL 的 SQL/PGQ 实现支持哪些标准特性,缺少哪些关键功能?

标准共定义 100 项特性,已提交的实现仅支持 26 项。支持的功能包括:带 WHERE 的图模式、顶点模式、边模式(完整和缩写形式)、标签析取(label1|label2)、属性引用、GRAPH_TABLE、DDL(含 ALTER 和 DROP)、每个元素表多个标签、以及将视图作为元素表。缺少的关键功能主要涉及五种机制:量词({n,m}、{n}、+、*)、路径模式(WALK、TRAIL、SIMPLE、ACYCLIC,当前行为等同 WALK 且关键字被拒绝)、路径搜索前缀(仅支持 ALL)、匹配模式(仅支持 REPEATABLE ELEMENTS)、以及一个 MATCH 中多个逗号分隔的路径模式(补丁已存在但推迟到量词之后)。

为什么 SQL/PGQ 从 PostgreSQL 19 中被撤回了?

SQL/PGQ 于 2026 年 3 月 16 日提交到 PostgreSQL 19,但在 9 月初,pgsql-hackers 邮件列表中关于其目录表示/定义和对象依赖问题的讨论最终决定将其移除。审查者认为应从 19 中撤回,以便在 20 中妥善且容易地修复问题,而不受向后兼容性压力。该功能预计在后续版本(20)中回归,重写引擎本身不会被重新设计,因此变长边、多路径模式等后续工作可以建立在同一基础上。

变长边(VLE)为什么是 SQL/PGQ 未来实现的重点和难点?

变长边是图从业者最先询问的功能,也是人们选择图查询语言而非写 JOIN 的常见原因。其难度与规模成正比:两三个跳的有界模式可以用类似的重写转换为 JOIN,但可能达到数百跳或无限运行直到耗尽图的模式则是不同的问题,涉及内存、终止和循环处理等自身要求。标准定义了多个与 VLE 交互的机制(模式跨度、可接受路径、返回哪些路径),每种组合意味着不同的执行策略。实现方式包括:重写为递归 CTE(基于集合的 BFS)、按长度展开为 UNION ALL 分支(无法处理无界情况,Oracle 采用此法)、或执行器级 DFS/BFS(需新执行器节点)。未来实现需考虑所有这些策略。

在 PostgreSQL 中定义属性图时,标签(LABEL)是如何工作的?

标签用于对表进行分组。在示例中,force_user 标签覆盖 jedi 和 sith 两个顶点表,place 标签覆盖 planet 和 station 两个顶点表,fought_at 标签覆盖 fought_on_planet 和 fought_on_station 两个边表。图本身不存储数据,在 PostgreSQL 中是一种新的关系类型(RELKIND_PROPGRAPH),存在于 pg_class 中但没有堆和列,行为类似视图。五个新目录(pg_propgraph_element、pg_propgraph_label 等)记录其元素、标签和属性。

SQL/PGQ 与纯 SQL 在查询图数据时有何不同?以查找 Alice 五跳内可达的人为例。

在 SQL/PGQ 中,该查询是单个模式:SELECT c.name FROM GRAPH_TABLE(social_graph MATCH (a IS Person WHERE a.name = 'Alice')-[IS KNOWS]->{1,5}(c IS Person) COLUMNS (c.name)) AS g; 而在纯 SQL 中需要使用 WITH RECURSIVE 递归 CTE,手动编码深度限制、用数组携带路径以避免循环,并在最后移除种子行。虽然 PostgreSQL 14 添加了 SEARCH 和 CYCLE 子句隐藏部分簿记,但查询仍是递归不动点计算。对于最短路径或轨迹(不重复边)等更复杂的图模式,纯 SQL 会更加棘手。

为什么 SQL/PGQ 必须实现在 PostgreSQL 核心中,而不能作为扩展?

PostgreSQL 没有解析器钩子(parser hook),扩展无法添加语法。解析器钩子曾被多次提出和讨论但从未落地。因此 SQL/PGQ 只有两个选择:在核心中实现,或者像 Apache AGE 那样在运行时解析字符串(后者属于扩展而非核心功能)。如果扩展能够扩展解析器,SQL/PGQ 本可以在树外构建和验证,然后再考虑纳入 contrib 模块或核心。这并非主张必须存在解析器钩子,而是说明缺乏钩子的成本最终落在何处。

🏷️

标签

➡️

继续阅读