3、关系型数据库中的图建模:邻接表、关系表、SQL表示图归纳总结

咱们做反欺诈图谱衍生变量,第一步就是把图数据塞进关系型数据库里。这不难,但选错建模方式,后面写递归查询时就等着哭吧。我这些年踩过的坑,全是在“存图”阶段埋下的。

邻接表模型——最直接的父子关系

邻接表,说白了就是在表里加一列 parent_id 指向上一级。比如给用户表加个 invited_by 字段,就能表示推荐关系链。这种模型直观,新手一看就懂。

CREATE TABLE user (
    id       INT PRIMARY KEY,
    name     VARCHAR(50),
    invited_by INT REFERENCES user(id)   -- 邻接关系
);

但注意了,邻接表只适合“每个节点最多一个父节点”的树形结构。反欺诈场景里,一个设备关联多个账号、一个账号关联多个IP,这全是多对多网络。用邻接表硬做,你会写出一堆自连接,性能还极差。我曾经在一个项目里用邻接表存储设备指纹图,跑一个两层深度遍历就要十几秒……后来全改了。

避坑: 邻接表不能表示“多父节点”或“环”。如果图中有环,自查询会无限递归,必须用 WITH RECURSIVE 的 CYCLE 子句处理,但早期 MySQL 不支持,踩过这个坑的人不只我一个。

关系表模型——显式的边存储

真正的图建模,我们得把“边”单独拎出来。一张节点表存实体,一张边表存关系。这才是关系型数据库里最通用的图模型。

CREATE TABLE node (
    id      INT PRIMARY KEY,
    label   VARCHAR(20)   -- 'USER','DEVICE','IP'等
);

CREATE TABLE edge (
    from_node INT REFERENCES node(id),
    to_node   INT REFERENCES node(id),
    rel_type  VARCHAR(20),  -- 'LOGIN','SHARE_DEVICE'
    weight    FLOAT,
    created_at TIMESTAMP,
    PRIMARY KEY (from_node, to_node, rel_type)
);

关系表的好处是:

  • 支持任意度数的边(多对多、子环、自环)
  • 可以给边加属性(时间、权重、类型)——做衍生变量的核心
  • 索引优化后,两跳遍历秒级响应

我建议在生产环境里,用关系表作为反欺诈图数据库的底层存储。邻接表只适合做“管理层级”这种固定树结构,别乱用。

SQL中的图遍历——递归CTE

图存进去了,怎么查?关系型数据库没有内置图遍历,但SQL标准提供了递归CTE。这玩意儿就像一把瑞士军刀,能处理大部分图查询需求。

WITH RECURSIVE reachable AS (
    -- 起点
    SELECT from_node, to_node, 1 AS depth
    FROM edge
    WHERE from_node = 1001
    
    UNION ALL
    
    -- 递归步
    SELECT e.from_node, e.to_node, r.depth + 1
    FROM edge e
    INNER JOIN reachable r ON r.to_node = e.from_node
    WHERE r.depth < 5
)
SELECT to_node, depth FROM reachable;

你想想看,仅用SQL就能查出某个账号所有间接关联的节点,最多5层。这在反欺诈里太常用了——比如查一个异常IP扩散到了多少设备。

但注意了,递归CTE在处理超大图(千万级边)时,如果深度超过10层,性能会断崖式下跌。原因在于关系型数据库是基于行的迭代,不是向量化计算。我一般会加两个限制:深度不超过6层,且每条查询都要用索引覆盖。

技巧: 建联合索引 (from_node, to_node, rel_type) 和 (to_node, from_node),让递归CTE的内连接走索引。不加索引时,两次迭代后全表扫描,CPU直接打满。

三种方式对比归纳

光说理论不如一张表直观。我把实际项目经验整理成下面的对比:

模型 适用场景 遍历能力 边属性支持 索引效率 我的推荐
邻接表 树形层级(组织架构、推荐链) 弱(深度遍历性能差) 无 中等 仅用于固定树结构
关系表 多对多网络(设备-账号-IP) 强(配合递归CTE) 丰富(时间、权重、类型) 高(双表索引分开) 首选方案
SQL递归CTE 图遍历查询 中等(深度≤6层性能可接受) 依赖边表 依赖索引设计 必须掌握的技能

下面这张图概括了三种建模方式的关系和选择路径,你存图时可以参考这个逻辑:

图建模方式选择流程图 图数据(节点+边) 树形结构? 邻接表模型 关系表模型 SQL递归CTE遍历 推荐用于反欺诈图谱

核心经验:反欺诈图谱一定是多对多网络,直接用关系表。别被邻接表的简单迷惑了——我曾经在早期项目里犯过这错误,后来重构时改边表花了三天,连带影响了上层全部查询SQL。一次选对,省半年。

这三种方式不是互斥的。你可以在同一个数据库里混用:管理层级用邻接表,设备关联网络用关系表,然后统一通过递归CTE对外提供图查询接口。但要记住,不管用哪种,索引设计和深度限制是永远逃不掉的对立面。