2、血缘采集技术:SQL解析原理、ETL脚本解析、存储过程血缘提取、日志解析方法
血缘采集,说白了就是搞清楚数据从哪来、经过谁、变成了啥。这是整个数据治理的基石。我做了这么多年血缘分析,最深的体会是:采集不准,后面全是白搭。今天咱们就聊聊血缘采集的四种核心技术,都是我在项目里实打实踩过坑、填过土的经验。
2.1 SQL解析原理:血缘采集的“基本功”
SQL解析是血缘采集最核心的技术。你想想看,数据仓库里90%以上的加工逻辑都是SQL,搞不定SQL解析,血缘分析就无从谈起。
SQL解析的本质,就是把一段SQL文本变成计算机能理解的语法树。我习惯把它分成三步:
- 词法分析:把SQL拆成一个个,比如SELECT、FROM、WHERE、表名、字段名。
- 语法分析:根据SQL语法规则,把组装成抽象语法树(AST)。
- 语义分析:在AST上做类型检查、表名解析、字段绑定。
举个例子,下面这条简单的SQL:
SELECT a.user_id, b.order_amount
FROM dim_user a
JOIN fact_order b ON a.user_id = b.user_id
WHERE b.dt = '2024-01-01'
解析后的AST大概长这样:
核心要点:AST的根节点是Query,下面挂两个子节点——SelectList和FromClause。SelectList里有两个ColumnRef(user_id和order_amount),FromClause里有两个TableRef(dim_user和fact_order)和一个JoinCondition。血缘提取就是遍历这棵树,找到所有ColumnRef和TableRef,建立“读-写”关系。
我在项目中遇到过最头疼的问题,是子查询和CTE。比如:
WITH tmp AS (
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id
)
SELECT t.user_id, t.total
FROM tmp t
WHERE t.total > 1000
这种嵌套结构,解析器需要先处理CTE的定义,把tmp当成一个临时表,然后再解析外层查询。如果解析器不支持CTE,血缘关系就会断掉。嗯,这里要注意:市面上很多开源解析器对CTE的支持都不完整,选型时要特别留意。
2.2 ETL脚本解析:从“黑盒”到“白盒”
ETL脚本比SQL复杂得多。它通常包含数据抽取、转换、加载三个环节,而且脚本语言五花八门——Shell、Python、Perl、甚至Java。我见过最离谱的项目,ETL脚本里混着SQL、Python和Shell,还互相调用。
解析ETL脚本的核心思路是:找到数据流动的“管道”。我个人习惯的做法是:
- 识别数据源和目标:脚本里哪些是读数据的操作(如JDBC连接、文件读取),哪些是写数据的操作(如INSERT、文件输出)。
- 提取中间转换逻辑:脚本里对数据做了哪些处理(如过滤、聚合、字段映射)。
- 建立调用链:脚本A的输出是不是脚本B的输入?有没有依赖关系?
避坑指南:我曾经接手过一个项目,ETL脚本里大量使用eval()动态执行代码。这种“动态生成”的逻辑,静态解析根本抓不到。后来我们不得不改用运行时采集——在脚本执行时hook住数据读写API,才把血缘关系补全。所以,如果你们的ETL脚本里有大量动态执行,静态解析基本没戏,得考虑运行时方案。
下面是一个典型的ETL脚本解析示例:
# 伪代码:ETL脚本血缘提取
def extract_etl_lineage(script):
# 1. 解析数据源
sources = find_all_read_operations(script)
# 2. 解析数据目标
targets = find_all_write_operations(script)
# 3. 解析转换逻辑
transforms = find_all_transformations(script)
# 4. 建立血缘关系
lineage = build_lineage(sources, transforms, targets)
return lineage
2.3 存储过程血缘提取:最“难啃”的骨头
存储过程是血缘采集的“硬骨头”。为什么?因为存储过程里什么都有——游标、临时表、动态SQL、异常处理、循环嵌套。我见过一个存储过程写了3000行,里面嵌套了7层循环,还用了动态SQL拼接表名。这种代码,人看都费劲,机器解析就更难了。
存储过程血缘提取的难点主要有三个:
| 难点 | 原因 | 我的应对方法 |
|---|---|---|
| 动态SQL | 表名、字段名在运行时才确定 | 静态解析+运行时补录,双管齐下 |
| 临时表 | 临时表生命周期短,跨会话不可见 | 跟踪临时表的创建和销毁,建立临时血缘 |
| 游标循环 | 逐行处理,血缘关系是“多对多” | 把游标展开成集合操作,再提取血缘 |
我记得有一次,客户的核心存储过程用了大量动态SQL拼接:
CREATE PROCEDURE sp_dynamic_query(@table_name VARCHAR(100))
AS
BEGIN
DECLARE @sql NVARCHAR(MAX)
SET @sql = 'SELECT * FROM ' + @table_name + ' WHERE dt = ''2024-01-01'''
EXEC sp_executesql @sql
END
这种代码,静态解析只能看到@table_name这个变量,根本不知道实际会查哪个表。我们后来做了个“运行时采集器”,在存储过程执行时拦截sp_executesql的调用,把实际执行的SQL抓出来,再解析血缘。说白了,静态搞不定的,就上动态。
2.4 日志解析方法:最后的“兜底方案”
当SQL解析、ETL脚本解析、存储过程提取都搞不定的时候,日志解析就是最后的“兜底方案”。
日志解析的原理很简单:数据库和ETL工具在执行SQL时,都会记录日志。我们只要解析这些日志,就能还原出数据流动的轨迹。常见的日志源包括:
- 数据库审计日志:记录所有SQL执行情况,包括查询、插入、更新、删除。
- ETL工具日志:如Informatica、DataStage、Kettle的执行日志。
- 应用日志:业务系统打印的数据操作日志。
日志解析的流程一般是:
- 日志采集:从各个系统拉取日志文件,或者通过syslog、Kafka实时接收。
- 日志解析:把非结构化的日志文本,解析成结构化的“谁、在什么时间、对哪个表、做了什么操作”。
- 血缘还原:把解析出来的操作,按时间顺序串联成血缘链路。
注意:日志解析有两个“坑”。第一,日志量巨大——一个中型数仓一天能产生几十GB的审计日志,解析性能是个大问题。第二,日志格式不统一——不同数据库、不同版本的日志格式都不一样,解析器要能适配多种格式。我曾经因为Oracle和MySQL的审计日志格式差异,多花了两周时间做适配。
下面是一个简单的日志解析示例:
# 伪代码:审计日志解析
def parse_audit_log(log_line):
# 假设日志格式: [2024-01-01 10:00:00] user=app01, sql=SELECT * FROM orders
timestamp = extract_timestamp(log_line)
user = extract_user(log_line)
sql = extract_sql(log_line)
# 解析SQL,提取表名和操作类型
tables = parse_sql_tables(sql)
operation = parse_sql_operation(sql)
return {
'timestamp': timestamp,
'user': user,
'tables': tables,
'operation': operation
}
2.5 四种技术的选型建议
这四种技术不是互斥的,而是互补的。我建议的选型策略是:
| 场景 | 推荐技术 | 原因 |
|---|---|---|
| 纯SQL加工 | SQL解析 | 准确率高,性能好 |
| 复杂ETL脚本 | ETL脚本解析+日志解析 | 脚本解析抓静态,日志解析补动态 |
| 存储过程为主 | 存储过程提取+运行时采集 | 静态+动态结合,覆盖最全 |
| 异构系统多 | 日志解析为主 | 统一日志格式,降低适配成本 |
说白了,没有一种技术能解决所有问题。我做了这么多项目,最后都是多种技术组合使用。比如,先用SQL解析处理大部分简单场景,再用存储过程提取处理复杂逻辑,最后用日志解析兜底。这样虽然前期投入大,但血缘的完整性和准确性最高。
2.6 血缘采集技术全景图
下面这张图,是我根据多年经验总结的血缘采集技术全景。它展示了四种技术的定位和关系:
从这张图能看出来,血缘采集不是单一技术能搞定的。我建议团队在落地时,先评估数据源的类型和复杂度,再选择合适的组合方案。比如,如果你们的数仓以Hive SQL为主,那SQL解析就能覆盖80%的场景;如果大量使用存储过程,那存储过程提取和运行时采集就是必选项。