3. 血缘关系建模:表级、字段级、过程级与存储结构

血缘分析这件事,做浅了就是画个线,做深了就是搭骨架。我见过不少团队,一上来就追求字段级血缘,结果数据链路一复杂,图根本跑不动。反过来,只做表级血缘又太粗糙,根本定位不到具体问题。

所以这一章,咱们把血缘的「粒度」和「存法」彻底聊透。说白了,就是回答三个问题:

  • 血缘到底分几层?
  • 每层能解决什么问题?
  • 存到哪儿最合适?

3.1 表级血缘:最基础的「谁依赖谁」

表级血缘,就是看表与表之间的上下游关系。比如 ods_order → dwd_order_detail → dws_order_summary。它不关心字段,只关心表。

我在项目中遇到过,有一次数据质量告警说「订单金额对不上」。我第一反应不是查字段,而是先拉出表级血缘图,看看哪些表引用了 dwd_order_detail。结果发现一张被遗忘的 tmp_order_backup 表还在跑旧逻辑。你看,表级血缘在「影响分析」场景下特别管用。

表级血缘的核心价值:
  • 快速定位数据链路断裂点
  • 评估表变更的影响范围
  • 辅助数据资产盘点

存储上,表级血缘最简单。关系型数据库里一张 table_lineage 表就够了:

CREATE TABLE table_lineage (
    id BIGINT PRIMARY KEY,
    source_table VARCHAR(255),
    target_table VARCHAR(255),
    etl_job_id VARCHAR(100),
    created_at TIMESTAMP
);

嗯,这里要注意:表级血缘虽然简单,但别只存「直接依赖」。我建议把「传递依赖」也预计算出来,否则做影响分析时要递归查很多层,性能扛不住。

3.2 字段级血缘:精细到「列对列」

字段级血缘,才是真正能帮我们修数据质量的利器。它告诉你 dwd_order.amount 是从 ods_order.amount 直接映射过来的,还是经过 price * quantity 计算得来的。

举个例子。有一次我发现 dws_report.revenue 比财务系统少了 0.5%。查字段级血缘发现,中间有一层做了 ROUND(amount, 0),把小数截掉了。这种问题,表级血缘根本看不出来。

我的经验:字段级血缘的难点不在存储,而在解析。SQL 解析器要能识别 SELECT 子句中的表达式、函数、子查询。我建议先用 sqlparse 或 ANTLR 做一层抽象,别自己手写正则。

字段级血缘的存储结构,关系型数据库可以这样设计:

CREATE TABLE field_lineage (
    id BIGINT PRIMARY KEY,
    source_table VARCHAR(255),
    source_field VARCHAR(255),
    target_table VARCHAR(255),
    target_field VARCHAR(255),
    transform_expression TEXT,
    etl_job_id VARCHAR(100)
);

但说实话,字段级血缘一旦上了规模,关系型数据库就有点吃力了。一张表几百个字段,上下游链路一多,JOIN 查询慢得让人抓狂。这时候,图数据库的优势就出来了。

3.3 过程级血缘:把「怎么算的」也记下来

过程级血缘,很多人会忽略。它不光记录「谁到谁」,还记录「经过了什么处理」。比如:

  • 字段 A 经过 SUM() 聚合后变成字段 B
  • 字段 C 和字段 D 通过 CASE WHEN 逻辑生成字段 E
  • 一张表经过 JOIN、FILTER、UNION 后生成新表

我为什么强调过程级血缘?因为数据质量问题的根因,往往藏在「过程」里。字段级血缘告诉你「谁影响了谁」,过程级血缘告诉你「怎么影响的」。比如一个字段值突然变成 NULL,过程级血缘能帮你定位到是哪个 LEFT JOIN 没匹配上,还是哪个 WHERE 条件过滤掉了。

避坑指南:我曾经在一个项目中,只做了字段级血缘,没记录过程。结果数据出问题时,只能看到「字段 A → 字段 B」,但中间有个 DISTINCT 去重操作导致数据丢失,查了三天才找到。从那以后,我坚持把「过程节点」也建模进去。

过程级血缘的存储,我建议用图数据库。因为过程节点本身就是一种「中间实体」,用关系型数据库存会变成一张巨大的 edge 表,查询效率极低。

3.4 血缘图的存储结构:图数据库 vs 关系型数据库

这是很多团队纠结的问题。我直接说结论:没有银弹,看场景选。

对比维度 图数据库(Neo4j、JanusGraph) 关系型数据库(MySQL、PostgreSQL)
查询模式 深度遍历、路径查询、多跳关联 点查、简单 JOIN、聚合统计
字段级血缘查询 快(O(n) 级别) 慢(需要递归 CTE 或多表 JOIN)
过程级血缘建模 天然支持(节点+边) 需要额外设计中间表
变更影响分析 秒级返回全链路 分钟级(数据量大时)
运维成本 高(需要专门团队) 低(现有 DBA 即可)
数据一致性 弱(最终一致性) 强(ACID)
适合场景 字段级+过程级血缘、影响分析 表级血缘、元数据管理、报表

我个人习惯是:表级血缘用关系型数据库,字段级和过程级血缘用图数据库。这样既保证了基础查询的稳定性,又让深度分析有了性能保障。

下面这张图,是我在项目中常用的血缘建模结构:

血缘关系建模三层结构 表级血缘层 ods_order dwd_order dws_report 字段级血缘层 amount price * qty (表达式) revenue 过程级血缘层 LEFT JOIN WHERE 过滤 GROUP BY

你看,三层结构各司其职。表级血缘负责「宏观链路」,字段级血缘负责「精准定位」,过程级血缘负责「根因分析」。三者配合,才能把数据质量修复这件事做透。

3.5 图数据库建模实战(以 Neo4j 为例)

如果你决定用图数据库,我建议这样建模:

// 节点类型
(:Table {name: 'ods_order', schema: 'ods', db: 'hive'})
(:Field {name: 'amount', table: 'ods_order', data_type: 'decimal'})
(:Process {type: 'expression', detail: 'price * quantity'})

// 关系类型
(:Field)-[:MAP_TO {transform: 'direct'}]->(:Field)
(:Field)-[:DERIVED_FROM {expression: 'SUM(amount)'}]->(:Field)
(:Table)-[:CONTAINS]->(:Field)
(:Process)-[:INPUT]->(:Field)
(:Process)-[:OUTPUT]->(:Field)

查询「某个字段的上游全链路」:

MATCH (f:Field {name: 'revenue', table: 'dws_report'})
CALL apoc.path.expand(f, 'MAP_TO|DERIVED_FROM|INPUT|OUTPUT', 'Field|Process', 1, 10)
YIELD path
RETURN path

这个查询,在关系型数据库里可能要写几十行递归 CTE,而且性能堪忧。但在 Neo4j 里,毫秒级返回。

一个小建议:别把所有血缘数据都塞进图数据库。元数据信息(表描述、字段注释、创建时间)还是放关系型数据库,图数据库只存「拓扑关系」。这样既保证了查询性能,又降低了运维复杂度。