2026-02-09 | 从全量血缘 → 异常检测的决策过程与实施方案
前置工作中完成了表级数据流依赖图(见 DB 依赖关系图),发现了:
自然延伸的问题是:能否把同样的分析下沉到字段级别? 即对每个字段追溯写入来源(数据血缘 / Data Lineage)。
一个健康的字段应有完整的生命周期,任何一环缺失即为异常:
| 维度 | 异常类型 | 说明 | 严重度 |
|---|---|---|---|
| 存在性 | 死字段 | Schema 有定义,代码中没有任何 INSERT/UPDATE 写入 | 高 |
| 只写不读 | 字段被写入但从未被 SELECT 使用 | 中 | |
| 代码引用不存在的列 | SQL 语句引用了 Schema 里没有的字段名 | 高 | |
| 一致性 | 多源写入 | 同一字段被 3+ 个不同 API 入口写入,可能有一致性风险 | 中 |
| 写入值域不匹配 | 代码写入的值与 Schema 类型/约束不一致 | 高 | |
| 完整性 | 缺约束的外键 | 字段名形如 *_id 但没有 FK 约束 |
中 |
| 隐式写入 | 只被 trigger / DB function 写入,应用层不知道 | 低 | |
| 冗余性 | DEFAULT 从未覆盖 | 列有 DEFAULT 值且代码从未写入过非默认值 | 低 |
分析脚本需要交叉比对三个数据源:
graph LR A["information_schema.columns\n(Schema 真相)"] --> D["交叉比对引擎"] B["代码中 INSERT/UPDATE\n(写入扫描)"] --> D C["代码中 SELECT\n(读取扫描)"] --> D D --> E["异常报告\n(仅输出问题字段)"] style A fill:#312e81,stroke:#6366f1 style B fill:#064e3b,stroke:#10b981 style C fill:#78350f,stroke:#f59e0b style D fill:#1e293b,stroke:#475569 style E fill:#7f1d1d,stroke:#ef4444
SELECT table_name, column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = 'tenant_xxx' ORDER BY table_name, ordinal_position;
扫描所有 backend/**/*.js 中的 INSERT/UPDATE SQL 语句,提取:
扫描所有 SELECT 语句,提取被读取的表和字段。注意 SELECT * 需特殊处理(标记为"读取全部")。
pg_trigger + 函数源码中提取定位:尽力而为的 lint 工具,不追求 100% 准确率,但高严重度发现应具有高置信度。
| 分析 | 粒度 | 输出 | 状态 |
|---|---|---|---|
| DB 数据流依赖图 | 表级 | 死表、只读表、只写表、孤立表 | 已完成 |
| 死表清理 + API 补全 | 表级 | 删 10 张死表、补 9 个 API 端点 | 已完成 |
| 字段级异常检测 | 字段级 | 死字段、多源写入、缺约束等 | 待实施 |
讨论结论:放弃全量字段血缘表,改为字段级异常检测报告。
理由:全量血缘表输出大、保鲜难、审查效率低。异常检测遵循与表级分析相同的"只报告偏离正常模式"原则,输出可执行行动项。
下一步:编写分析脚本,交叉比对 Schema + 代码 INSERT/UPDATE + 代码 SELECT,输出异常报告。