土法炼钢兴趣小组的算法知识备份

【图数据库内核】Cypher 计划入口:Estimated Rows、基数乘积与「看起来对但跑爆」

文章导航

分类入口
databasegraph
标签入口
#graph-database#neo4j#cypher#query-plan#cardinality#profile#explain#cost-planner#estimated-rows

目录

09 篇把 Expand 算子钉住。本篇回答:规划器如何挑算子树,以及为何每个算子「看起来合理」时整查询仍会爆。 核心量不是毫秒广告,而是 行(Rows)在算子之间如何增殖,以及 Estimated Rows 在幂律图上如何骗人

本文是「图数据库内核」系列第 10 篇(共 16 篇)。→ 系列目录

篇目 核心内容
第 9 篇 · Expand 遍历算子语义
第 10 篇 · Cypher 计划 EXPLAIN/PROFILE、基数、计划事故
第 11 篇 · 事务与锁 并发下的另一类「跑不动」

版本锚定:Cypher Manual current / 5 Understanding query plansQuery tuning(Planner COST/IDP、inferSchemaParts 等)。基数管理实践交叉参考 Neo4j KB Tuning Cypher queries by understanding cardinality(B 级;适用版本标注偏 3.5–4.4,机制与 current 计划列一致处采用)。不粘贴本机未跑 PROFILE;手册示例数字仅作列形态说明。


一、查询生命周期:声明式 → 逻辑计划 → 物理执行

手册把一条 Cypher 分成:

  1. 解析声明式字符串;
  2. 规划器(optimizer)产出逻辑计划——在当前库状态(索引、约束、统计)下估计哪条执行路径更便宜;
  3. Runtime 把逻辑计划变成物理执行(如 PIPELINED)。

规划器默认 COST(与 planner=idp 同义):在受限搜索空间内找估计代价最低的计划。planner=dp 可穷尽搜索,规划时间可能显著变长——只在调计划本身时偶用。

统计与 schema 变了,缓存计划可能过时;可用 CYPHER replan=force EXPLAIN … 在低峰强制重规划(只规划不执行)。5.21+inferSchemaParts 控制是否从关系类型等推断标签以改进行估计——有助于「带依赖标签」(如每个 :Actor 也是 :Person)时的估计,细节见 Query tuning


二、怎么读计划表

2.1 EXPLAIN vs PROFILE

EXPLAIN PROFILE
是否执行 (含写查询——会改库)
Estimated Rows
Rows / DB Hits / Memory 等
用途 看算子形状与估计 真实行数对照估计、定位爆炸层
成本 更高;手册:非主动调优时不要随便 PROFILE

2.2 自下而上

计划是算子的二叉树。表从上到下打印时,数据从叶子向上流:最底行是扫描/Seek,最顶是 ProduceResults。父算子吃子算子产出的行流。

2.3 必看列

含义(手册) 调优用法
Estimated Rows 规划器估计本算子产出行数;用于选计划 与 Rows 比;来自标签/索引统计与选择性模型,无运行时反馈,可错——当提示而非测量
Rows 实际产出行数(PROFILE) Support/KB:多数算子按行执行;Rows 尖峰强烈指示下游工作量
DB Hits 存储层低级访问计数(实体/属性/索引项等) 一行可对应多次 hits;≠ Rows;反映存贮层干活量(接第 03–05 篇)
Page Cache Hits/Misses EE;缓存命中 冷热缓存不可比(第 05 篇)
Memory 算子峰值堆;页脚 total 是查询峰值 勿把各算子 Memory 简单相加
Indexes Used 用了哪些索引、访问次数 核对是否真走了你以为的索引
ProduceResults          ← 读:结果
   ↑
Filter / Expand / …
   ↑
NodeIndexSeek / Scan    ← 读:起点

三、基数:行流才是第一公民

KB 把 cardinality 定义为算子之间流动的行数。两条不变量:

  1. 算子对输入的每一行执行;
  2. 算子产出新的行流给父算子。

因此:

与第 09 篇衔接:变长路径「按路径出行」时,Rows 可以远大于「唯一终点数」;Pruning + DISTINCT 同时改算子与基数叙事。


四、代价模型在赌什么——以及幂律如何作弊

规划器近似:

[ () _{} c()() ]

\(\widehat{|Rows|}\) 来自标签计数、索引选择性等统计,并把谓词选择性组合起来。工程实现里常见独立性假设(谓词/边彼此独立)——DeepWiki 等对源码的转述指向 AssumeIndependence… 一类模型;本篇把它标为机制方向,不以第三方转述当 A 级字面。

幂律图上的系统性偏差(本系列开放问题,第 01–02 篇已立靶):

估计用的「平均」 真实
标签上平均度数 \(\bar{d}\) 少数点 \(d_{\max}\gg\bar{d}\)
属性等值选择性 热点值极度倾斜
多跳 \(f^k\) 路径上撞上超节点后一层炸穿

于是出现教科书事故:

  1. Estimated Rows 很小 → 规划器选 Nested Expand / 某 Join 顺序;
  2. 实际 Rows 在某一 Expand 飙升 → CPU、堆、DB Hits、page fault 一起爆;
  3. 每个叶子仍显示 IndexSeek——「用了索引」与「查询健康」无关。

这就是标题里的「看起来对但跑爆」:计划树局部都是合法算子,乘积被错估的基数撕开。


五、经典计划事故(对照前几篇)

5.1 高召回入口 × 深 Expand

第 07 篇:IndexSeek 产出巨大 \(|S|\)
第 09 篇:每行再乘 \(f\)
计划:Seek → Expand → Expand… 每步 Estimated 可能仍「乐观」。
对策:收紧谓词;早聚合/DISTINCT;降跳数;建模拆超节点——不是再加一个更宽的索引。

5.2 行积后的第二次 MATCH

MATCH (m)<-[:ACTED_IN]-(a)   -- |rows| = #actors
MATCH (m)<-[:DIRECTED]-(d)   -- 对每一行再匹配导演 → 行积

KB Movies 示例:同一 m 出现在多行时,从 m 再匹配会冗余执行。应用 collect / pattern comprehension / WITH DISTINCT m 先把基数压回去。

5.3 Estimated ≪ Rows 的变长

无上界或上界过大的 *..k:估计按平均连通性,实际路径枚举指数涨。对照第 09 篇:加界、DISTINCT→Pruning、量化路径内谓词。

5.4 强迫错索引(USING)

USING INDEX 可覆盖规划器选择。若强迫低基数属性上的 Seek,入口 Rows 本身就大——提示成功、执行失败。以 PROFILE 的 Rows 为准,不以「用了索引」为准。

5.5 缓存与 PROFILE 误读

Page Cache Misses 高可能只是冷缓存(第 05 篇);重启后第一次 PROFILE 与稳态不可比。先看 Rows 爆炸层,再看 Misses。

5.6 EXPLAIN 好看、PROFILE 写爆堆

Estimated 全程个位数,实际中间路径物化把事务内存打满(第 05 篇「伪 I/O」)——扩 pagecache.size 无效,要砍行流或跳数。


六、调优检查单(计划层)

  1. EXPLAIN 看形状:起点是否 IndexSeek/合理 Scan?Expand 是 All 还是 Into?有无可怕的笛卡尔 Apply?
  2. PROFILE(只在调优时)找 Rows 第一处数量级跳变的算子——多半是事故点。
  3. 对比 Estimated Rows vs Rows:差几个数量级 → 不信任该计划的代价排序,用查询重写/提示/统计新鲜度干预。
  4. 聚合 / DISTINCT / 早 LIMIT(注意 LIMIT 在写操作之后的惰性语义,KB 有警告)管理基数。
  5. 变长:上界 + 是否只要终点(第 09 篇)。
  6. 统计与索引变更后 replan;大变更后不要死抱旧计划缓存。
  7. DB Hits 高但 Rows 不高 → 回第 03–05 篇(指针追逐、属性链、冷页);Rows 高 → 先留在本篇砍基数。

七、和系列坐标系闭合

计划层对应
02 代价模型 \(\widehat{f}\) vs 真 \(f\)\(d_{\max}\)
07 索引 叶子算子与 \(|S|\)
08 语义索引 过程/SEARCH 结果再进 MATCH 时的行流
09 Expand 行增殖的主要机器
05 page cache PROFILE 的 Hits/Misses;次于 Rows
11 锁 计划「快」但仍等待——另一诊断轴

八、争论与开放问题

  1. 代价规划 vs 提示/重写:A 侧信任统计 + COST;B 侧在图负载上默认不信任平均度数。本系列立场:用 Estimated↔︎Rows 落差决定信任度,而不是站队。
  2. 独立性假设:谓词相关(标签蕴含、类型与度数相关)时估计偏乐观或悲观都常见;inferSchemaParts 是产品侧补丁之一,不是银弹。
  3. 开放问题:面向幂律的度数直方图是否进入官方基数模型;并行/流水线 runtime 下 Rows 与 Time 列的解读(手册:融合管道共享 Time);与 GDS 分析计划的边界。

谱系:代价优化来自数据库查询处理传统(System R 以降);图模式匹配把「连接」换成拓扑扩张后,同一套基数语言、更歪的数据分布——争论从「会不会用索引」移到「敢不敢信 \(f^k\)」。


九、来源与实验台账(本篇)

结论 等级 来源
查询生命周期;EXPLAIN/PROFILE;列语义;自下而上读法;估计无运行时反馈 A Cypher Manual Understanding query plans
COST/IDP/DP;replan;inferSchemaParts(5.21+) A Query tuning
按行执行、行积、DISTINCT/聚合管理基数、变长与 Pruning 条件实践 B KB understanding-cypher-cardinality(与第 09 篇手册 Pruning 交叉)
幂律下估计失真 工程判断 / 开放问题 标明非官方定理
本机 PROFILE 案例 未跑 不伪造 Estimated/Rows 表

十、小结

  1. 计划为估计代价而选;执行按真实行流付钱。 调优看 Rows,不看「有没有 IndexSeek」奖杯。
  2. Estimated Rows 可错——尤其在超节点与深路径上;与 Rows 的落差就是诊断信号。
  3. 基数管理(DISTINCT、聚合、早限流、查询重排)与第 09 篇算子选择是同一件事的两面。
  4. 下一篇:当计划与行流都健康,仍慢或卡住时——锁、隔离与关系写入的图特有点。

系列目录 · 上一篇:Expand · 下一篇:事务与锁

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .