供应链图谱实战:BOM 建模、追溯与风险传导

系统讲解供应链图谱的建模与实战:为什么供应链天然是图、BOM 与批次的多层建模、物料与供应商的双重网络、上游影响分析与反查、追溯与召回定位、多级供应商风险传导与暴露度计算、与 ERP/MES 的数据集成、图模型与关系模型的取舍、以及性能陷阱与治理排错。

引言

供应链是天然适合图建模的场景,但大多数企业仍然把它塞在关系库里:一张 bom_parent_child 表存父子物料,一张 batch_trace 表存批次流向,查询时靠递归 CTE 反复自连接。这在两三层的 BOM 上还能忍,一旦遇到「这个成品里的某个电容,是哪个供应商哪一批的原材料做的,还流向了哪些其他成品」这类问题,递归查询的深度、方向、与批次维度的组合会立刻把 SQL 写成不可维护的一团。更棘手的是供应链问题本质上都是图问题:上游影响分析是反向可达性,召回定位是「从问题批次出发找所有受影响成品」的正向可达性,风险传导是带权的最短路径与影响力传播,替代料与多源采购是同一物料节点上的多条入边。本文按实战视角讲供应链图谱:先讲为什么供应链适合图、BOM 与批次怎么建模(含「物料网络」与「供应商网络」的双重结构)、上游影响分析与反查、追溯与召回定位、多级供应商风险传导与暴露度计算,再讲与 ERP/MES 的数据集成、图与关系模型的取舍,最后是性能陷阱与治理排错。目标:你能把一张 BOM 与批次流水建成可追溯、可反查、可算风险的供应链图谱。

前置:图建模模式与反模式 、图数据导入与 ETL 、时态图与时间维度建模 。


目录


1. 供应链为什么适合图建模

供应链的四类问题,全是图问题:

1. 上游影响分析(反查):某个原材料断供,会影响哪些成品与订单?
   → 反向可达性(沿 BOM 的入边向上)
2. 追溯与召回(正查):某批原料有问题,它流向了哪些成品与客户?
   → 正向可达性(沿批次流向的边向下)
3. 风险传导:某供应商出事,它的风险如何沿供应网络扩散?
   → 带权路径 + 影响力传播(衰减系数)
4. 替代与寻源:这个物料还有哪些合格供应商?换了料影响哪些产品?
   → 同层多入边 + 变更影响分析
→ 四个问题都是「沿边遍历 + 路径计算」,关系库要写递归 CTE,
  图库是原生的一跳写法

关系库做供应链的四个具体痛点:

1. 递归深度不确定:BOM 可能是 3 层也可能是 12 层,
   SQL 的 WITH RECURSIVE 要写深度上限,改结构就要改查询
2. 多维度组合:物料 × 批次 × 供应商 × 时间,
   关系表 JOIN 的组合数随维度指数增长
3. 方向敏感:正查与反查在关系库里是两套索引两套 SQL
4. 路径语义弱:想表达「只经过合格供应商的路径」这类约束
   在 SQL 里要写成多层过滤,图里就是一条模式
→ 结论:供应链是「深度不定 + 多维度 + 方向敏感」的典型图场景

但不要为了图而图:以下场景关系库反而更合适。

- 纯报表与聚合(按月的采购金额统计)→ 列存或数仓
- 单表 CRUD(维护供应商主数据)→ 关系库
- 固定的两层展开(只看直接子件)→ 关系库一条 JOIN 就够
→ 判断标准:查询是否涉及「深度不定的多跳 + 路径语义」,
  是则上图,否则留在关系库,两者用 ID 对齐

心智:供应链的四类核心问题(上游影响、召回追溯、风险传导、替代寻源)本质都是「沿边遍历加路径计算」,关系库要用递归 CTE 并受深度上限、多维度组合、方向敏感三重折磨;但纯报表、单表 CRUD、固定两层展开仍应留在关系库,上图的标准是「深度不定的多跳加路径语义」。


2. BOM 与批次的多层建模

先分清两个网络:物料网络(BOM)与批次网络(流向)。

物料网络(类型级 / Type-level):
  节点:物料(Material/SKU)
  边:BOM 组成关系(父件 -[:REQUIRES {qty, unit}]-> 子件)
  特点:相对稳定(改设计才变),规模小(几万到几十万节点)
  用途:影响分析、替代料、成本展开

批次网络(实例级 / Instance-level):
  节点:批次(Batch/Lot)、序列号、工单
  边:消耗与产出(工单 -[:CONSUMES]-> 原料批次,
                  工单 -[:PRODUCES]-> 成品批次)
  特点:持续增长(每天新增),规模大(千万到十亿)
  用途:追溯、召回、质量分析
→ 关键决策:两个网络共用一张图还是分开?
  共用可跨层查询但图大且两类查询互相干扰;分开各自优化但要两步查
→ 常见折中:同一实例、不同标签与索引,用标签区分热冷

BOM 建模的三个要点:

1. 边上的属性是业务核心:用量(qty)、单位、损耗率、
   生效/失效日期、替代组 —— 用量必须放边上而非节点上,
   因为同一个子件在不同父件里用量不同
2. 版本与生效期:BOM 有版本(工程变更 ECO),查询必须带
   「截至某日期有效的 BOM」→ 用边的 valid_from / valid_to 表达
3. 层级显式还是隐式:显式存 level 可加速「只看前三层」但改结构要
   全量重算;隐式靠跳数更灵活但每次要算 → 建议隐式为主,
   对高频的「前三层」查询做物化加速

批次建模的三个要点:

1. 批次是最小追溯单元:同物料不同批次必须分开建节点,
   否则「哪一批有问题」无法回答
2. 工单是连接点:原料批次 -[CONSUMES]-> 工单 -[PRODUCES]-> 成品批次,
   工单节点让「多对多」的消耗与产出关系变清晰
3. 时间戳是必需属性:生产时间、入库时间、发货时间,
   追溯查询几乎总要带时间过滤
→ 反模式:把批次做成物料节点上的属性(lots: ["L1","L2"]),
  这样无法表达「L1 流向了 A、L2 流向了 B」,追溯直接失效

建模示例:

// 物料网络:BOM 组成关系(带用量与有效期)
MERGE (p:Material {code: 'PCB-001'})
MERGE (c:Material {code: 'CAP-100NF'})
MERGE (p)-[:REQUIRES {qty: 12, unit: 'pcs',
                       valid_from: date('2026-01-01'), valid_to: null}]->(c);

// 批次网络:工单消耗与产出
MERGE (wo:WorkOrder {id: 'WO-2026-0001'})
MERGE (wo)-[:CONSUMES {qty: 1200, ts: datetime('2026-03-02T08:00:00Z')}]->
      (:Batch {lot: 'LOT-CAP-77', material: 'CAP-100NF'})
MERGE (wo)-[:PRODUCES {qty: 100, ts: datetime('2026-03-02T18:00:00Z')}]->
      (:Batch {lot: 'LOT-PCB-31', material: 'PCB-001'});

两个网络的关联:批次节点上带 material 属性,或建 :INSTANCE_OF 边指向物料节点。推荐显式建边——「这个批次是什么物料」是追溯查询的必经一步,显式边可走索引,用属性则每次要字符串匹配。

心智:供应链建模必须分清两个网络:物料网络(BOM,类型级,稳定且小)与批次网络(流向,实例级,持续增长且大);BOM 的用量与有效期放边上、版本靠生效期表达;批次必须独立成节点并用工单节点连接消耗与产出;把批次做成属性是让追溯失效的头号反模式。


3. 上游影响分析与反查

问题:「某原材料供应商断供,会影响哪些成品、哪些订单、多少金额?」这是一次反向可达性遍历。

// 反查:从问题物料出发,向上找出所有受影响的成品
MATCH (bad:Material {code: $materialCode})
MATCH path = (bad)<-[:REQUIRES*1..8]-(up:Material {type: 'FINISHED_GOOD'})
RETURN DISTINCT up.code AS finishedGood, length(path) AS levels,
       [n IN nodes(path) | n.code] AS bomPath
ORDER BY levels ASC LIMIT 500;
三个必须注意的点:
1. 跳数上界(*1..8):没有上界会在数据异常(BOM 有环)时无限展开
2. 数量上界(LIMIT):一个通用件可能出现在上千个成品里
3. DISTINCT:同一条路径可能被多条 BOM 版本重复命中
→ 影响分析的输出通常是一批成品,要控制返回规模并分页

BOM 有环是真实存在的:数据录入错误、返工件(成品回到产线当原料)、循环替代,都会造成环。查询必须有保护:

// 检测 BOM 环:沿 REQUIRES 走 8 跳能回到起点即存在环
MATCH (m:Material) MATCH path = (m)-[:REQUIRES*1..8]->(m)
RETURN DISTINCT m.code AS cyclicMaterial, length(path) AS cycleLength LIMIT 100;
环的后果:反查遍历会重复展开、结果数量爆炸;成本展开会无限递归
→ 校验阶段跑一次环检测,把有环物料拉出来修数据;
  查询侧同时保留跳数上界,双重保险

影响分析必须带业务维度,否则「受影响成品数」通常大到无法行动:

// 影响分析:受影响成品 + 在手订单金额
MATCH (bad:Material {code: $materialCode})
MATCH (bad)<-[:REQUIRES*1..6]-(up:Material {type: 'FINISHED_GOOD'})
MATCH (o:Order)-[:ORDERS]->(up)
WHERE o.status IN ['OPEN', 'IN_PRODUCTION']
RETURN up.code AS finishedGood, count(DISTINCT o) AS affectedOrders,
       sum(o.amount) AS exposure
ORDER BY exposure DESC LIMIT 100;

暴露度(exposure)的三种算法:订单金额直接求和(简单,但会重复计算共享件的影响);按用量加权(子件用量越大影响越大,qty 在边上);按库存覆盖天数(有替代料或库存的成品实际不受影响)。业务上最有用的是「净暴露 = 受影响订单金额 - 可替代与可覆盖部分」。

替代料让「影响」不等于「断供」:查询影响时必须考虑替代组——先用 IN_GROUP 找到问题物料所属的替代组,再看组内是否还有 status = 'QUALIFIED' 的可用替代料及其交期(leadTimeDays)。若存在交期可接受的替代料,则真实影响要按替代比例打折,否则会把「有备份」的成品也算成高危。

心智:上游影响分析是反向可达性遍历,必须同时设跳数上界、数量上界与 DISTINCT;BOM 环是真实数据问题,要在校验阶段检测并在查询侧保留上界;影响分析要算净暴露(订单金额扣除替代料与库存覆盖)而不是只报受影响成品数。


4. 追溯与召回定位

问题:「某批原料被检出问题,它流向了哪些成品、哪些客户、哪些地区?」这是一次正向可达性遍历,且必须沿着「实际消耗」而非「BOM 计划」。

// 追溯:从问题批次出发,正向找所有下游成品批次
MATCH (src:Batch {lot: $badLot})
MATCH path = (src)-[:CONSUMED_BY|PRODUCED_BY*1..10]->(down:Batch)
RETURN DISTINCT down.lot AS downstreamLot, down.material AS material,
       length(path) AS hops, down.producedAt AS producedAt
ORDER BY hops ASC, producedAt DESC LIMIT 1000;
追溯与影响分析的关键区别:
- 影响分析走「物料网络 + 计划 BOM」(可能发生但尚未发生)
- 追溯走「批次网络 + 实际消耗」(确实发生过)
- 两者结果可以不一致:计划 BOM 里有替代料,实际生产可能用了另一批
  → 只有批次网络能回答「真的用了哪批」
→ 召回必须用追溯(实际),影响分析才用 BOM(计划)

召回定位的三个层次:

层次 1:直接下游(成品批次)—— 用了问题批次的 PCB 批次有哪些
层次 2:间接下游(成品 → 客户/地区)—— 装了它的整机发给了谁
层次 3:影响评估(数量与时间窗)—— 受影响数量、时间窗、已发 vs 在库
→ 层次 2 与 3 决定召回范围(发到哪些国家要按当地法规处理),
  只报层次 1 的成品清单不足以支撑决策
// 召回范围:受影响批次 → 发货记录 → 客户与地区
MATCH (src:Batch {lot: $badLot})
MATCH (src)-[:CONSUMED_BY|PRODUCED_BY*1..10]->(down:Batch)
MATCH (down)-[:SHIPPED_AS]->(ship:Shipment)-[:TO]->(cust:Customer)
RETURN cust.name AS customer, ship.region AS region,
       count(DISTINCT down) AS affectedLots, sum(ship.qty) AS affectedQty,
       min(ship.shippedAt) AS earliestShip, max(ship.shippedAt) AS latestShip
ORDER BY affectedQty DESC;

召回查询的两个工程要求:可解释(每条结果能回溯到完整路径,监管要求「为什么这批被召回」,应保留 path 或 hop 序列供导出成报告);可执行(结果必须带「在哪里、多少量、什么时候」三个字段)。

时间窗过滤是召回的核心约束:问题批次的时间属性决定召回边界——若问题在 2026-03-02 的工单被发现,则之后用了该批的成品都要查。时间窗能大幅缩小遍历范围(比全量追溯快一个数量级),因此批次节点上必须有时间属性并建索引;追溯查询的第一条过滤条件应该是时间而不是物料。

心智:追溯走「批次网络加实际消耗」,影响分析走「物料网络加计划 BOM」,两者结果可能不同,召回必须用追溯;召回定位要覆盖「直接下游、客户与地区、数量与时间窗」三个层次;时间窗是最有效的遍历收窄条件,批次节点必须带时间属性并建索引。


5. 多级供应商风险传导

问题:「某二级供应商所在地区发生灾害,对我方成品的风险暴露是多少?」供应链风险传导有三个特点:逐级衰减(越远影响越小,但非线性的——关键物料断供影响远大于通用件);非线性(单源物料风险极高,多源可分摊);可叠加(多个上游同时出问题会累积到同一成品)。因此不能简单用跳数当距离,必须给边加权。

边的权重怎么定:

权重 = f(采购金额占比, 是否单源, 库存覆盖天数, 替代难度)
常见简化:weight = 采购占比 × 单源系数(单源 1.0,多源 0.3~0.5)
路径的「风险距离」= 各级权重之和(或乘积,取决于语义)
→ 加法适合「多个供应商共同贡献风险」
→ 乘法适合「串联依赖,任一级断裂则整条链断」
→ 语义必须写清,否则算出来的数字无法解释
// 从受灾地区的供应商出发,找出受影响的成品与风险分
MATCH (s:Supplier)-[:LOCATED_IN]->(r:Region {code: $regionCode})
WHERE r.status = 'DISRUPTED'
MATCH (s)-[:SUPPLIES {riskWeight: $w}]->(m:Material)
MATCH path = (m)<-[:REQUIRES*1..6]-(fg:Material {type: 'FINISHED_GOOD'})
WITH fg, path, s,
     reduce(risk = 0.0, rel IN relationships(path) |
            risk + coalesce(rel.criticality, 0.5)) AS bomRisk
RETURN fg.code AS finishedGood, collect(DISTINCT s.name) AS affectedSuppliers,
       count(DISTINCT path) AS pathCount,
       round(max(bomRisk) * $w, 3) AS riskScore
ORDER BY riskScore DESC LIMIT 50;
这段查询的三个要点:
1. reduce() 沿路径累加关键度(criticality 放在 BOM 边上)
2. collect(DISTINCT) 聚合「同一成品被多个供应商影响」
3. 结果要能排序(riskScore)—— 排序才是「可行动」的前提
→ 常见错误:只返回路径清单不返回分数,业务无法排优先级

单源风险是最高优先级信号:单源物料(只有一个合格供应商)是供应链最脆弱的一环,风险传导系数接近 1。查法很简单——用 MATCH (m:Material)<-[:SUPPLIES]-(s:Supplier) WHERE s.status='QUALIFIED' 后按 size(collect(DISTINCT s)) = 1 过滤,再沿 [:REQUIRES*1..6] 反查受影响的成品。它通常是「已知问题」,业务要的是清单而非模型,成本低而价值高——供应链风险项目的第一版功能,通常就是这个查询。

风险传导的四个实现选择:用 GDS 的最短路与带权路径(适合「找最关键的一条传导路径」);用 Cypher 变长路径加 reduce(适合「列出所有受影响成品」);用带衰减的影响力传播算法(适合「供应商重要度排序」而非具体清单);预计算高频区域的物化映射。多数场景用后两者即可,不必上 GDS。

心智:供应链风险传导必须给边加权(采购占比 × 单源系数),用加法还是乘法取决于语义并要写清;风险查询要返回可排序的风险分而不只是路径清单;单源物料分析是成本最低、价值最高的第一版功能;实现上多数场景用「变长路径加 reduce」加「高频结果物化」即可。


6. 实践:从 BOM 到影响分析

场景:一家电子制造企业,BOM 有 12 层,物料 8 万,批次 2 亿,要支持「某物料断供影响哪些订单」的秒级查询。

第一步:按查询建索引:

CREATE CONSTRAINT material_code IF NOT EXISTS
FOR (m:Material) REQUIRE m.code IS UNIQUE;
CREATE CONSTRAINT batch_lot IF NOT EXISTS FOR (b:Batch) REQUIRE b.lot IS UNIQUE;
CREATE INDEX batch_material IF NOT EXISTS FOR (b:Batch) ON (b.material);
CREATE INDEX batch_produced IF NOT EXISTS FOR (b:Batch) ON (b.producedAt);
CREATE INDEX material_type IF NOT EXISTS FOR (m:Material) ON (m.type);
索引选择的依据(对着查询看):
- 影响分析从 Material.code 出发 → material_code 唯一约束
- 追溯从 Batch.lot 出发 → batch_lot 唯一约束
- 追溯的时间窗过滤 → batch_produced 索引
- 「按物料找批次」→ batch_material 索引
→ 没有这几条索引,所有查询都会退化成标签全扫

第二步:导入时做数据质量校验:三项校验——BOM 环检测(MATCH (m:Material) MATCH path = (m)-[:REQUIRES*1..8]->(m) 统计有环物料数);悬空引用(REQUIRES 指向不存在的物料编码,用 NOT exists() 过滤后计数);用量异常(r.qty <= 0 OR r.qty > 100000)。取舍是:环必须阻断(否则所有遍历查询都有风险),悬空引用告警并跳过该边,用量异常仅告警(可能是真实的大用量,如螺钉)。校验结果要落成报告,作为导入流程的一部分而非事后补。

第三步:对关键物料物化影响关系:

// 只对关键物料物化「物料 → 受影响成品」的前 6 层结果
MATCH (m:Material) WHERE m.isCritical = true
MATCH (m)<-[:REQUIRES*1..6]-(fg:Material {type: 'FINISHED_GOOD'})
MERGE (m)-[:IMPACTS]->(fg);
物化的取舍:
- 收益:影响分析从「遍历 6 层」变成「一跳查 IMPACTS」,秒级 → 毫秒级
- 代价:BOM 变更后要重建(建议 nightly 全量 + 变更时增量)
- 适用:只对关键物料(高价值、单源、高风险)物化,而不是全量
  (8 万物料 × 平均 20 个成品 = 160 万条边,规模可接受)
→ 「关键」的判定规则要写进数据字典并可配置

第四步:把结果做成可行动的报表:

// 影响分析报表:成品 + 订单 + 净暴露(扣除替代料)
MATCH (bad:Material {code: $code})
OPTIONAL MATCH (bad)-[:IMPACTS]->(fg:Material)
OPTIONAL MATCH (o:Order)-[:ORDERS]->(fg)
WHERE o.status IN ['OPEN', 'IN_PRODUCTION']
WITH fg, count(DISTINCT o) AS orders, coalesce(sum(o.amount), 0) AS grossExposure
OPTIONAL MATCH (bad)-[:IN_GROUP]->(:SubstituteGroup)<-[:IN_GROUP]-(alt:Material)
WHERE alt.status = 'QUALIFIED'
RETURN fg.code AS finishedGood, orders, grossExposure,
       count(DISTINCT alt) AS alternatives,
       CASE WHEN count(DISTINCT alt) > 0
            THEN grossExposure * 0.3 ELSE grossExposure END AS netExposure
ORDER BY netExposure DESC LIMIT 100;

四个落地要点:索引对着查询建(不是见字段就加);导入阶段做环检测与悬空引用校验;只对关键物料物化影响关系并定期重建;报表必须给「净暴露」而不是「受影响清单」,否则业务无法排优先级。

心智:供应链图谱的落地四步是「按查询建索引 → 导入时做环与悬空校验 → 对关键物料物化影响关系 → 输出带净暴露的可行动报表」;物化只做关键物料,全量物化的收益与维护成本不成比例;报表的核心是排序与净暴露,而不是清单。


7. 性能与建模陷阱

四个高频性能陷阱:

陷阱 1:无上界的变长路径
  (m)<-[:REQUIRES*]-(fg) → 有环时无限展开,无环时也可能展开百万路径
  对策:永远写 *1..N,并在查询层加 LIMIT

陷阱 2:通用件(超节点)
  一个螺钉用在 5000 个成品里 → 从它出发的遍历会展开 5000 条路径
  对策:识别超节点(度数分位数统计),对其查询走物化路径,
        或在建模时区分「通用件」与「专用件」并分别处理

陷阱 3:批次网络的时间跨度
  2 亿批次若不做时间分区,追溯查询要扫全量
  对策:按时间分区(按年建标签或按时间范围裁剪子图),查询必带时间窗

陷阱 4:跨网络查询
  「物料 → 批次 → 工单 → 物料」的跨层查询会让两个网络互相放大
  对策:先在一侧收敛(拿到少量 ID),再查另一侧,而不是一次写完整个模式

建模的四个反模式:

1. 批次做成属性(lots: [...])→ 无法表达批次级流向,追溯失效
2. 层级用 level 字段硬编码 → 改结构要全量重算,且只支持固定层数
3. 供应商与物料混成一个节点类型 → 无法区分「谁供给谁」与「谁组成谁」
4. 用关系方向表达「上游/下游」两个语义 →
   同一对节点既可能是上下游也可能是替代关系,方向会歧义
→ 反模式的共同特征:为了查询方便而牺牲语义,后期无法补

通用件的识别与处理:用 MATCH (m:Material)<-[:REQUIRES]-() RETURN m.code, count(*) ORDER BY count(*) DESC 统计出度分布即可定位。处理有三招——物化(对通用件的「影响成品集」预计算并定期刷新)、剪枝(查询时先按类型或价值过滤,避免从通用件直接展开)、拆分(把通用件与专用件分成不同标签,查询时分别走不同路径)。通用件通常是 BOM 里度数最高的 1% 节点,却贡献大部分慢查询。

心智:供应链图谱的四个性能陷阱是无上界变长路径、通用件超节点、批次网络时间跨度、跨网络查询放大;四个建模反模式是批次做成属性、层级硬编码、供应商与物料混类、用方向表达双重语义;通用件是度最高的 1% 却贡献多数慢查询,必须物化、剪枝或拆分。


8. 与 ERP 的数据集成

ERP 是供应链图谱的数据源,集成的核心是「增量 + 幂等 + 对齐」。

数据来源与映射:
ERP 物料主数据 → :Material 节点
ERP BOM 表 → :REQUIRES 边(含用量与生效期)
MES 工单与批次 → :WorkOrder / :Batch 节点
WMS 出入库 → :Shipment / 库存快照
SRM 供应商与合同 → :Supplier 节点与 :SUPPLIES 边
→ 每个来源要有明确的「主键 → 图节点」映射与更新频率

增量同步的三种方式:时间戳轮询(按 last_modified 拉增量,实现简单、侵入小,但删除难捕获,需软删除配合);CDC(订阅数据库日志,实时且能捕获删除,代价是链路复杂);事件驱动(源系统主动推送业务事件,语义清晰但需源系统支持且事件可能丢)。供应链场景推荐 CDC 做实时 + 每日全量对账兜底——单纯依赖一种方式迟早会不一致。

幂等写入是必须的:

// 幂等:用 MERGE 而不是 CREATE,避免重放造成重复节点
MERGE (m:Material {code: $code})
SET m.name = $name, m.type = $type, m.updatedAt = datetime($ts);

// 幂等的边写入:先 MATCH 端点,再 MERGE 边
MATCH (p:Material {code: $parent})
MATCH (c:Material {code: $child})
MERGE (p)-[r:REQUIRES {valid_from: date($from)}]->(c)
SET r.qty = $qty, r.unit = $unit, r.valid_to = $to;
幂等的三个坑:
1. MERGE 整条模式会因属性差异创建重复边 → 拆成「先找端点再 MERGE 边」
2. 并发下 MERGE 可能创建重复节点(无唯一约束时)
   → 必须配唯一约束,让数据库来保证
3. 批次属性频繁变更(如质检状态)→ 用 SET 而不是重建节点

每日对账三项:节点数(图的 Material 数 vs ERP 物料数,差异超过 0.1% 告警);边数(REQUIRES 边数 vs BOM 表行数,考虑生效期过滤);抽样校验(随机抽 100 个物料比对 BOM 展开结果)。没有对账,图会静默地偏离 ERP,而供应链的结论全部基于图;对账结果要落成指标并告警,而不是写进日志里没人看。

与关系库共存的分工:留在关系库的是主数据维护(CRUD)、财务报表、固定两层展开;放到图库的是多跳影响分析、批次追溯、风险传导、替代寻源;两者共用业务主键(物料编码、批次号)对齐,不做 ID 映射表。关键认知是图库不做「唯一真相源」,它是有明确用途的查询加速层,写路径仍以 ERP 为准,图通过同步保持最终一致。

心智:与 ERP 集成要明确每个来源到图节点/边的映射与更新频率;推荐「CDC 实时 + 每日全量对账兜底」,单靠一种方式迟早不一致;写入必须幂等(MERGE 加唯一约束,注意 MERGE 整条模式会造重复边);图库定位是查询加速层而非真相源,写路径仍以 ERP 为准。


9. 排错与治理

供应链图谱的排错清单:

1. 追溯结果比预期少 → 批次网络有断链(工单未采集消耗关系)
   检查:每个成品批次是否都有 PRODUCES 入边
2. 影响分析结果爆炸 → BOM 有环或存在通用件,先跑环检测与度数统计
3. 查询突然变慢 → 图增长导致索引失效或 page cache 不足
   检查:数据量/内存比值、索引是否被查询计划命中
4. 追溯与影响分析结果不一致 → 正常(一个走实际、一个走计划),
   但若差异过大要查替代料执行记录是否缺失
5. 对账不一致 → 同步链路漏了删除(软删除未同步)
6. 幂等失败出现重复节点 → 缺唯一约束,或并发写入未走约束
7. 时间窗过滤无效 → 批次时间属性为 null 或未建索引
→ 按「数据完整性 → 数据质量 → 索引与规模 → 语义差异」顺序查

治理的三件事:数据质量门禁(导入前跑环检测、悬空引用、用量异常三项校验,环必须阻断、其余告警,结果落报告);查询规范(所有变长路径必须写上界与 LIMIT,通用件查询走物化路径,跨网络查询先收敛再展开);版本与审计(BOM 变更留痕——谁在什么时候改了哪条边,追溯结果要可解释、能回溯完整路径)。供应链图谱一旦用于质量与召回,结论会被监管引用,「可解释」与「可追溯」是硬要求。

关键指标的监控:图规模(物料数、批次数、BOM 边数、日增批次);查询性能(影响分析 p95、追溯 p95、超时率);数据质量(环数、悬空引用数、对账差异率);覆盖率(有多少成品批次有完整的前向追溯链)。其中覆盖率是最容易被忽略但最重要的指标——如果只有 60% 的批次能追溯到原料,召回结论就只对 60% 有效;这个指标应作为数据治理的北极星,持续提升。

什么时候该考虑分图:信号是批次网络超过内存容量、查询延迟不可控;物料网络与批次网络的查询互相干扰(一方慢查询拖垮另一方);不同业务线(如不同工厂)的图几乎无交集。做法有按时间分图(历史批次归档到只读图)、按业务线分图(各自独立实例,主数据同步)、按冷热分图(近期批次在热图,历史在冷图,查询时路由)。分图的代价是跨图查询变复杂,只有在单图确实撑不住时才做。

心智:供应链图谱排错按「数据完整性 → 数据质量 → 索引与规模 → 语义差异」顺序查;治理三件事是数据质量门禁、查询规范、版本与审计;「可追溯覆盖率」是最该监控的北极星指标;只有在批次网络超出内存或两类查询互相干扰时才考虑按时间、业务线或冷热分图。


10. 速查表

全篇速查:

主题结论
为什么用图四类问题都是「沿边遍历 + 路径计算」
不必用图纯报表、单表 CRUD、固定两层展开
两个网络物料网络(BOM,稳定小)+ 批次网络(流向,增长大)
BOM 边属性用量、单位、生效期、替代组都放边上
批次建模必须独立节点,用工单节点连接消耗与产出
头号反模式批次做成属性,追溯直接失效
影响分析反向可达性,必须设跳数上界与 LIMIT
BOM 环真实存在,校验阶段检测 + 查询侧上界
追溯走实际消耗(批次网络),非计划 BOM
召回三层次:直接下游、客户地区、数量时间窗
风险传导边加权(占比 × 单源系数),返回可排序风险分
单源分析成本最低价值最高的第一版功能
性能陷阱无上界路径、通用件、时间跨度、跨网络放大
集成CDC 实时 + 每日对账兜底,写入幂等
北极星指标可追溯覆盖率(有多少批次能追到原料)

一句话记忆:供应链的四类核心问题(上游影响、召回追溯、风险传导、替代寻源)本质都是「沿边遍历加路径计算」,因此天然适合图建模,但纯报表与固定两层展开仍应留在关系库;建模必须分清两个网络——物料网络(BOM,类型级,稳定且小,用量与生效期放边上)与批次网络(实例级,持续增长且大,批次必须独立成节点并由工单节点连接消耗与产出),把批次做成属性是让追溯失效的头号反模式;上游影响分析是反向可达性遍历,必须同时设跳数上界、LIMIT 与 DISTINCT,并在校验阶段检测 BOM 环;追溯走「批次网络加实际消耗」,与走「物料网络加计划 BOM」的影响分析结果可能不同,召回必须用追溯,且要覆盖直接下游、客户地区、数量时间窗三个层次,时间窗是最有效的收窄条件;风险传导必须给边加权(采购占比 × 单源系数)并返回可排序的风险分,单源物料分析是成本最低价值最高的第一版功能;性能上要防无上界变长路径、通用件超节点、批次时间跨度与跨网络查询放大四类陷阱,通用件是度数最高的 1% 却贡献多数慢查询;与 ERP 集成推荐「CDC 实时加每日全量对账兜底」,写入用 MERGE 加唯一约束保证幂等,图库定位是查询加速层而非真相源;最后用「可追溯覆盖率」作为数据治理的北极星指标——只有它能回答「召回结论覆盖了多少真实流向」。


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「graphdb」更多文章

  1. 查询缓存与物化视图
  2. 图数据测试策略与回归验证
  3. 图数据库并发控制与批量更新