UXAGE图数据库

1. 概述

UXAGE图数据库是一款同时支持SQL和openCypher双查询语言、统一处理关系与图结构数据的企业级产品,无需独立图数据库或数据迁移,适用于欺诈检测、知识图谱、智能推荐等复杂关系分析场景。其核心能力包括:

  1. openCypher 查询语言——使用声明式图查询语言 openCypher(兼容 Neo4j 查询语法)操作节点、关系与属性;
  2. SQL 与 Cypher 混合查询——图查询结果可与关系表 JOIN、用于 CTE、PL/uxSQL 函数,实现图 + 关系数据联合分析;
  3. 完整 ACID 事务——继承数据库的 MVCC 与事务机制,图操作具备企业级一致性保障;
  4. 统一存储与生态——图数据存储为普通表,备份恢复、流复制、时间点恢复(PITR)等运维能力开箱即用。

典型应用场景:知识图谱、社交网络分析(社群发现、二度人脉)、推荐系统、金融反欺诈(复杂交易网络分析)、GraphRAG(图增强检索生成)、Agent 记忆关系图。

2. 快速入门

以下为最简可运行流程,生产用法见后续章节。

2.1 启用扩展

在每个需要使用 UXAge 的数据库中执行一次(需要相应权限):

CREATE EXTENSION age;

2.2 加载扩展并设置搜索路径

每个会话连接后需加载 AGE 库,并将 ag_catalog 加入 search_path

LOAD 'age';
SET search_path = ag_catalog, "$user", public;

推荐在数据库级永久设置(免逐会话配置):

ALTER DATABASE mydb SET search_path = ag_catalog, "$user", public;

2.3 创建图

SELECT create_graph('graph_name');

2.4 插入图数据

SELECT * FROM cypher('graph_name', $$
    CREATE (n:Person {name: '张三', age: 30, role: 'Engineer'})
    RETURN n
$$) AS (node uxgtype);

2.5 查询图数据

-- 通过一跳关系模式匹配获取 Person 及其任职公司
SELECT * FROM cypher('graph_name', $$
    MATCH (p:Person)-[r:WORKS_AT]->(c:Company)
    RETURN p.name, c.name, r.since
$$) AS (person uxgtype, company uxgtype, since uxgtype);

要点:所有 Cypher 查询都通过 cypher() 函数执行,返回结果必须显式声明列名与类型(通常为 uxgtype)。

3. 核心概念

概念说明
Graph(图)一个命名的图空间,包含节点和边
Vertex(节点)图中的实体,可带标签和属性,可独立存在
Edge(边)节点之间的有向连接,必须恰好连接两个节点,必须有一个标签
Label(标签)节点或边的类型分类;顶点与边之间的标签名不能重叠
Property(属性)节点或边上的 key-value 键值对;属性名只能为字符串类型

3.1 图在数据库中的存储映射

UXAge 基于表结构实现图数据持久化,每个图对应一个独立的命名空间(schema):

  • 创建图时,系统自动在该命名空间下创建两张母表:_ag_label_vertex(顶点母表)和 _ag_label_edge(边母表);
  • 后续创建的任何顶点标签表和边标签表(如 PersonWORKS_AT)都继承自这两张母表;
  • 顶点表包含 idproperties 字段;边表包含 idstart_idend_idproperties 字段;properties 以 uxgtype(JSONB 扩展)编码存储;
  • 超大属性自动走 TOAST 压缩存储。

重要不要在图所属命名空间内禁止手工 DML;DDL 仅限创建索引,图结构由 UXAge 自动维护。

3.2 系统目录表

目录表说明
ag_catalog.ag_graph存储所有图的名字及其对应命名空间
ag_catalog.ag_label存储所有点集表和边集表的标签信息(名称、graph id、kind、对应关系表、序列名)
-- 查看所有图
SELECT * FROM ag_catalog.ag_graph;

-- 查看所有标签(kind: v = 顶点, e = 边)
SELECT * FROM ag_catalog.ag_label;

4. 安装与启用

4.1 版本兼容性

UXAge 运行于优炫数据库管理系统(UXDB)之上,具体支持的版本以产品发布说明为准。

4.2 启用步骤

-- 1. 创建扩展(每个数据库一次)
CREATE EXTENSION IF NOT EXISTS age;

-- 2. 每个会话加载库
LOAD 'age';

-- 3. 设置 search_path(会话级或数据库级)
SET search_path = ag_catalog, "$user", public;
ALTER DATABASE mydb SET search_path = ag_catalog, "$user", public;  -- 永久(推荐)

-- 4. 验证安装
SELECT extversion FROM ux_extension WHERE extname = 'age';

4.3 允许非超级用户使用

  1. 创建符号链接,允许非超级用户加载 AGE 库(路径按实际安装位置调整):
sudo ln -s /usr/lib/uxdb/lib/age.so /usr/lib/uxdb/lib/plugins/age.so
  1. 授予目标用户 ag_catalog schema 的 USAGE 权限:
GRANT USAGE ON SCHEMA ag_catalog TO db_user;

5. cypher() 查询格式

cypher()ag_catalog 中的函数,返回 SETOF records,是执行所有 Cypher 查询的唯一入口。

5.1 语法

cypher(graph_name, query_string, parameters)
参数说明
graph_name目标图名
query_string要执行的 Cypher 查询(用 $$ ... $$ 美元引用包裹)
parameters可选,Prepared Statements 的参数 map,默认 NULL;仅可用于预处理语句,否则报错

标准调用形式:

SELECT * FROM cypher('graph_name', $$
    /* Cypher Query Here */
$$) AS (result1 uxgtype, result2 uxgtype);

5.2 使用约束

  • 即使 Cypher 查询不返回结果,也必须定义列结构(AS (... uxgtype));
  • cypher() 不能直接作为表达式的一部分,也不能在 SELECT 子句中作为独立列调用——需要时使用子查询:
-- 不允许:ERROR: cypher(...) in expressions is not supported
SELECT cypher('graph_name', $$ MATCH (v:Person) RETURN v.name $$);

-- 正确:作为子查询使用
SELECT * FROM cypher('graph_name', $$
    MATCH (v:Person) RETURN v.name
$$) AS (name uxgtype);

6. 图与标签管理

6.1 管理函数

函数语法说明
create_graphcreate_graph(graph_name)创建图,自动建好所需表;无错误即成功
drop_graphdrop_graph(graph_name, cascade)删除图;cascade = true 时级联删除标签和数据(推荐),否则需手工 DDL 清理
create_vlabelcreate_vlabel(graph_name, label_name)显式创建顶点标签
create_elabelcreate_elabel(graph_name, label_name)显式创建边标签
SELECT * FROM ag_catalog.create_graph('new_graph');
SELECT * FROM ag_catalog.drop_graph('new_graph', true);
SELECT * FROM ag_catalog.create_vlabel('new_graph', 'Person');
SELECT * FROM ag_catalog.create_elabel('new_graph', 'FATHER_OF');

用 Cypher CREATE 语句创建顶点/边时会自动创建对应标签表,无需预先调用 create_vlabel / create_elabel

6.2 存储演示

SELECT create_graph('new_graph');

-- 使用 Cypher 创建图数据(自动生成 Person 标签表和 FATHER_OF 边表)
SELECT * FROM cypher('new_graph', $$
    CREATE (:Person {name: 'Daedalus'})-[:FATHER_OF]->(:Person {name: 'Icarus'})
$$) AS (a uxgtype);

-- 查看自动生成的标签表
SELECT name, graph, kind, relation FROM ag_catalog.ag_label;
--  _ag_label_vertex | ... | v | new_graph._ag_label_vertex
--  _ag_label_edge   | ... | e | new_graph._ag_label_edge
--  Person           | ... | v | new_graph."Person"
--  FATHER_OF        | ... | e | new_graph."FATHER_OF"

7. uxgtype 数据类型

uxgtype 是 UXAge 唯一的返回类型,是 JSON 的超集JSONB 的自定义实现,并扩展了 vertex、edge、path 等图类型。

7.1 与 PostgreSQL/JSONB 的差异

行为说明
布尔输出输出完整单词 true / false(Postgres 为 t / f
NaN 比较'NaN'::float = 'NaN'::float 求值为 true(为支持正确排序,偏离 IEEE 754)
null 语义遵循 Cypher 语义:null = null 返回 null 而非 true;WHERE 中非 true 一律按 false 处理
字符串输入用单引号,输出用双引号

7.2 类型清单

简单类型

类型说明
null缺失或未定义值;从顶点取不存在的属性产生 null
integer64 位整数,范围 ±9.2×10¹⁸
floatIEEE-754 不精确浮点;特殊值 Infinity-InfinityNaN 须加引号转换(如 '-Infinity'::float
numeric精确数值(推荐货币场景);必须显式 ::numeric 注解;最大精度 1000
bool三态:true / false / unknown(unknown 由 null 表示)
string支持转义序列:\t \b \n \r \f \' \" \\ \uXXXX

复合类型

类型说明
list方括号字面量;支持负索引、范围切片(lst[0..3] 不含结束索引);越界单元素返回 null,越界切片截断
map花括号字面量 {key: value},可嵌套 list 和 map;支持 m.keym.listKey[0] 访问

图实体类型

类型结构
vertex{id: graphid, label: 标签名, properties: {...}}::vertex
edge{id, start_id, end_id, label, properties}::edge(有向,恰好连接两个节点)
path顶点与边交替的序列 [...]::path(必须以顶点开始、至少含一条边)

graphid:图实体的唯一标识,由标签 ID(label id)与该标签内的序列号组合编码而成。注意比较不同图的实体时 ID 可能重叠。

7.3 类型示例

-- numeric 需显式注解
SELECT * FROM cypher('graph_name', $$
    RETURN 1.0::numeric
$$) AS (numeric_result uxgtype);

-- list 与 map
SELECT * FROM cypher('graph_name', $$
    WITH {int_key: 1, float_key: 1.0, bool_key: true,
          listKey: [{inner: 'Map1'}, {inner: 'Map2'}]} as m
    RETURN m.int_key, m.listKey[0].inner
$$) AS (i uxgtype, inner uxgtype);

8. Cypher 数据操作

以下示例沿用 knowledge_graph 图。完整语法参考 openCypher 规范Apache AGE 子句文档

8.1 创建(CREATE)

-- 创建带标签和属性的节点
SELECT * FROM cypher('knowledge_graph', $$
    CREATE (n:Person {name: '张三', age: 30, role: 'Engineer'})
    RETURN n
$$) AS (node uxgtype);

-- 批量创建节点
SELECT * FROM cypher('knowledge_graph', $$
    CREATE (a:Person {name: '李四', role: 'Manager'}),
           (b:Company {name: '优炫', industry: 'Database'}),
           (c:Technology {name: 'PostgreSQL', type: 'Database'})
    RETURN a, b, c
$$) AS (a uxgtype, b uxgtype, c uxgtype);

-- 创建关系(边):先 MATCH 端点,再 CREATE 关系
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (p:Person {name: '张三'}), (c:Company {name: '优炫'})
    CREATE (p)-[r:WORKS_AT {since: '2020-01-01'}]->(c)
    RETURN r
$$) AS (rel uxgtype);

8.2 查询(MATCH / WHERE / RETURN)

-- 查找所有 Person 节点
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (n:Person)
    RETURN n.name, n.role
$$) AS (name uxgtype, role uxgtype);

-- 关系模式匹配
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (p:Person)-[r:WORKS_AT]->(c:Company)
    RETURN p.name, c.name, r.since
$$) AS (person uxgtype, company uxgtype, since uxgtype);

-- WHERE 条件过滤
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (p:Person)-[r:WORKS_AT]->(c:Company)
    WHERE p.age > 25 AND c.name = '优炫'
    RETURN p.name
$$) AS (person uxgtype);

-- 多跳查询(同一起点跨多种关系)
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (p:Person)-[:WORKS_AT]->(c:Company), (p)-[:USES]->(t:Technology)
    RETURN p.name, c.name, t.name
$$) AS (person uxgtype, company uxgtype, tech uxgtype);

-- 可变深度路径(2-3 跳)
SELECT * FROM cypher('knowledge_graph', $$
    MATCH path = (a:Person)-[*2..3]->(b)
    RETURN path
$$) AS (path uxgtype);

8.3 更新(SET / REMOVE)

-- 更新节点属性
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (p:Person {name: '张三'})
    SET p.age = 31
    RETURN p
$$) AS (node uxgtype);

-- 删除属性
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (p:Person {name: '张三'})
    REMOVE p.role
    RETURN p
$$) AS (node uxgtype);

8.4 删除(DELETE / DETACH DELETE)

-- 删除关系
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (p:Person {name: '张三'})-[r:USES]->(t:Technology)
    DELETE r
$$) AS (result uxgtype);

-- DETACH DELETE:删除节点及其所有关联边(删除节点前必须先删边,或直接用 DETACH DELETE)
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (t:Technology {name: 'PostgreSQL'})
    DETACH DELETE t
$$) AS (result uxgtype);

8.5 合并(MERGE)

MERGE 按"存在则匹配、不存在则创建"语义去重,是实体幂等写入(如 GraphRAG 构建图谱)的关键语句:

SELECT * FROM cypher('knowledge_graph', $$
    MERGE (n:Person {name: '王五'})
    SET n.age = 28
    RETURN n
$$) AS (node uxgtype);

8.6 结果处理(ORDER BY / SKIP / LIMIT / WITH / UNWIND)

-- 排序 + 分页
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (n:Person)
    RETURN n.name
    ORDER BY n.name DESC
    SKIP 10
    LIMIT 5
$$) AS (name uxgtype);

-- WITH 管道传递中间结果
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (p:Person)-[:WORKS_AT]->(c:Company)
    WITH c, count(p) AS headcount
    WHERE headcount > 10
    RETURN c.name, headcount
$$) AS (company uxgtype, headcount uxgtype);

-- UNWIND 展开列表(批量导入常用)
SELECT * FROM cypher('knowledge_graph', $$
    UNWIND [{name: '赵六', age: 40}, {name: '孙七', age: 35}] AS row
    CREATE (p:Person {name: row.name, age: row.age})
    RETURN p
$$) AS (p uxgtype);

8.7 聚合

支持 count()sum()avg()min()max()collect() 等聚合函数:

SELECT * FROM cypher('knowledge_graph', $$
    MATCH (p:Person)-[:WORKS_AT]->(c:Company)
    RETURN c.name, count(p), collect(p.name)
$$) AS (company uxgtype, cnt uxgtype, names uxgtype);

9. SQL 与 Cypher 混合查询

UXAge 的核心优势之一:图查询结果可像普通子查询一样与关系数据联合处理。

9.1 图查询与关系表 JOIN

-- 图查询结果与关系表 users 联合
SELECT g.person_name, u.email, u.department
FROM (
    SELECT * FROM cypher('knowledge_graph', $$
        MATCH (p:Person)-[:WORKS_AT]->(c:Company {name: '优炫'})
        RETURN p.name AS person_name
    $$) AS (person_name uxgtype)
) g
JOIN users u ON u.name = g.person_name::text;

提示uxgtypetext 比较时需显式 ::text 转换。

9.2 CTE 与 Join 表达式中的 Cypher

-- CTE 中使用 Cypher
WITH graph_result AS (
    SELECT * FROM cypher('knowledge_graph', $$
        MATCH (p:Person)-[:USES]->(t:Technology)
        RETURN p.name, t.name
    $$) AS (person uxgtype, tech uxgtype)
)
SELECT person::text, tech::text FROM graph_result;

9.3 PL/uxSQL 函数中使用 Cypher

CREATE OR REPLACE FUNCTION get_colleagues(person_name TEXT)
RETURNS TABLE(name uxgtype) AS $$
DECLARE
	p uxgtype := uxgtype_build_map('name', person_name);
BEGIN
    RETURN QUERY
    SELECT * FROM cypher('knowledge_graph', $$
        MATCH (p:Person {name: $name})-[:WORKS_AT]->(c)<-[:WORKS_AT]-(co)
        RETURN co.name
    $$, p) AS (name uxgtype);
END;
$$ LANGUAGE pluxsql;

9.4 Prepared Statements(参数化查询)

cypher() 的第三个参数仅可用于预处理语句,防止注入并支持执行计划复用:

PREPARE q(uxgtype) AS
SELECT * FROM cypher('knowledge_graph', $$
    MATCH (p:Person {name: $name})
    RETURN p.role
$$, $1) AS (role uxgtype);

EXECUTE q(uxgtype_build_map('name', '张三'));

10. 索引与性能优化

10.1 索引

UXAge 的图数据底层是普通表,可直接对标签表的属性建索引:

-- 在节点标签的属性上创建 B-tree 索引(等值/范围查询)
CREATE INDEX ON knowledge_graph."Person" ((properties->>'name'));

-- GIN 索引适合属性键值对的多条件查询
CREATE INDEX ON knowledge_graph."Person" USING GIN (properties);

-- 边表:为 start_id / end_id 建 B-tree 索引,加速关系遍历
CREATE INDEX ON knowledge_graph."WORKS_AT" (start_id, end_id);

索引选型建议:

索引类型适用场景
B-treeID、start_id、end_id 等值/范围查询;单属性精确匹配
GINuxgtype/JSON 属性的包含类查询
表达式索引只针对高频属性键建更小、更高效的索引

10.2 查询优化

  • 使用 EXPLAIN (ANALYZE, BUFFERS) 分析执行计划,确认是否命中索引、比较估计行数与实际行数;
  • 明确指定关系方向——无向查询 (a)-[r]-(b) 会双向展开,性能远差于 (a)-[r]->(b)
  • 限制路径跳数上限:可变深度遍历([*])不设上限可能导致全图遍历,应写成 [*1..3] 形式;
  • 大结果集使用 LIMIT 分页;
  • 图遍历本质上是多次自连接,深度遍历性能依赖端点索引。

10.3 存储与系统参数(DBA)

  • 节点/边超大属性自动走 TOAST 压缩,监控 TOAST 表大小可评估存储效率;
  • shared_buffers 建议为物理内存的 25%–40%;
  • work_mem 按查询复杂度与并发量设置(典型 10MB–100MB),图多跳查询的排序/哈希内存消耗较大;
  • maintenance_work_mem 在建索引、大批量导入时调大;
  • 批量导入:先 LOAD 数据、后建索引;
  • 定期 VACUUM / ANALYZE 清理死元组并更新统计信息;碎片化严重时 REINDEX

10.4 事务与并发

  • UXAge 完整继承 ACID:Cypher 操作与普通 SQL 同处一个事务,失败即整体回滚;
  • 基于 MVCC 的读写并发:读不阻塞写、写不阻塞读;
  • 与普通表一致,遵循标准隔离级别语义。

11. 典型应用场景

11.1 知识图谱

SELECT create_graph('product_kg');

SELECT * FROM cypher('product_kg', $$
    CREATE (pg:Product {name: 'UXDB', category: 'Database'}),
           (vec:Feature {name: '向量检索', desc: '基于 UXVector'}),
           (graph:Feature {name: '图计算', desc: '基于 UXAge'}),
           (pg)-[:HAS_FEATURE]->(vec),
           (pg)-[:HAS_FEATURE]->(graph)
    RETURN pg, vec, graph
$$) AS (pg uxgtype, vec uxgtype, graph uxgtype);

11.2 GraphRAG(图增强检索)

将知识图谱(UXAge)与向量检索(UXVector)结合:先向量召回相关文档,再从图中扩展相关实体的关系链,图上下文 + 文档内容一并送入大模型生成回答。相比纯向量 RAG,可支持多跳推理全局视角

安全要点:由 LLM 抽取的实体类型/关系类型若要拼入 Cypher 标签,必须经过白名单校验(禁止任意字符串直接拼入查询),属性值使用参数化或安全转义:

-- 白名单校验 + format(%I/%L) 安全拼接种标签与属性
CREATE TABLE IF NOT EXISTS app.allowed_entity_types (type TEXT PRIMARY KEY);
INSERT INTO app.allowed_entity_types(type) VALUES ('Person'), ('Company'), ('Technology');

-- 仅放行白名单内的类型,其余跳过并记日志
PERFORM * FROM cypher('knowledge_graph', format($$
    MERGE (n:%I {name: %L})
    SET n.description = %L
$$, e_type, e_name, e_desc)) AS (v uxgtype);

11.3 Agent 记忆关系图

SELECT * FROM cypher('agent_memory', $$
    CREATE (a1:Agent {name: 'Planner', role: 'planning'}),
           (a2:Agent {name: 'Researcher', role: 'research'}),
           (a1)-[:DELEGATES {task: '调研竞品', timestamp: '2026-01-15'}]->(a2)
    RETURN a1, a2
$$) AS (a1 uxgtype, a2 uxgtype);

12. 注意事项与常见问题

问题说明
忘记设置 search_path?必须将 ag_catalog 加入 search_path(或使用全限定名 ag_catalog.cypher(...)),否则函数不可见
查询报 "cypher(...) in expressions is not supported"cypher() 不能作为表达式或 SELECT 独立列,改用子查询 / CTE / JOIN
删除节点报有边关联?DELETE 关联边,或直接使用 DETACH DELETE
图数据存在哪里?每个图一个命名空间,顶点/边分别存于继承 _ag_label_vertex / _ag_label_edge 的标签表中
可以手工操作图命名空间的表吗?不建议。不要在图命名空间内执行任何 DML / DDL,交由 UXAge 管理
标签命名限制?顶点与边之间标签名不能重叠;边必须有标签,顶点标签可选
属性类型限制?属性名只能是字符串;属性值支持 uxgtype 标量与复合类型
不同图的实体 ID 能直接比较吗?不能。graphid 由标签 ID + 序列构成,跨图可能重叠
如何删除整个图?drop_graph('graph_name', true)cascade=true 级联清理标签与数据
备份恢复?使用数据库原生备份恢复工具(如 ux_dump / ux_restore),图数据随库一同备份;支持 WAL 归档与 PITR
非超级用户报无法加载库?参见第 4.3 节:创建 plugins/age.so 符号链接并 GRANT USAGE ON SCHEMA ag_catalog
null 比较不符合预期?uxgtype 遵循 Cypher 语义:null = null 返回 null;判断空值用 IS NULL

参考资料