UXAGE图数据库
1. 概述
UXAGE图数据库是一款同时支持SQL和openCypher双查询语言、统一处理关系与图结构数据的企业级产品,无需独立图数据库或数据迁移,适用于欺诈检测、知识图谱、智能推荐等复杂关系分析场景。其核心能力包括:
- openCypher 查询语言——使用声明式图查询语言 openCypher(兼容 Neo4j 查询语法)操作节点、关系与属性;
- SQL 与 Cypher 混合查询——图查询结果可与关系表 JOIN、用于 CTE、PL/uxSQL 函数,实现图 + 关系数据联合分析;
- 完整 ACID 事务——继承数据库的 MVCC 与事务机制,图操作具备企业级一致性保障;
- 统一存储与生态——图数据存储为普通表,备份恢复、流复制、时间点恢复(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(边母表); - 后续创建的任何顶点标签表和边标签表(如
Person、WORKS_AT)都继承自这两张母表; - 顶点表包含
id、properties字段;边表包含id、start_id、end_id、properties字段;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 允许非超级用户使用
- 创建符号链接,允许非超级用户加载 AGE 库(路径按实际安装位置调整):
sudo ln -s /usr/lib/uxdb/lib/age.so /usr/lib/uxdb/lib/plugins/age.so
- 授予目标用户
ag_catalogschema 的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_graph | create_graph(graph_name) | 创建图,自动建好所需表;无错误即成功 |
drop_graph | drop_graph(graph_name, cascade) | 删除图;cascade = true 时级联删除标签和数据(推荐),否则需手工 DDL 清理 |
create_vlabel | create_vlabel(graph_name, label_name) | 显式创建顶点标签 |
create_elabel | create_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 |
integer | 64 位整数,范围 ±9.2×10¹⁸ |
float | IEEE-754 不精确浮点;特殊值 Infinity、-Infinity、NaN 须加引号转换(如 '-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.key 与 m.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;
提示:
uxgtype与text比较时需显式::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-tree | ID、start_id、end_id 等值/范围查询;单属性精确匹配 |
| GIN | uxgtype/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 |