【图数据库内核】Cypher 计划入口:Estimated Rows、基数乘积与「看起来对但跑爆」
内容提要
本文介绍Cypher查询计划与基数管理。规划器基于统计估计行数选择执行路径,但幂律图上平均度数失真,导致Estimated Rows低估实际行数,引发Expand爆炸。调优需用PROFILE对比真实Rows,通过DISTINCT、聚合、早LIMIT控制基数,而非依赖索引提示。
延伸解读
Estimated Rows 为何会骗人
规划器依赖标签计数、索引选择性等统计来估计行数,并常假设谓词独立。在幂律图上,平均度数严重失真,少数超节点导致实际行数远超估计。因此,Estimated Rows 只是提示,不是测量结果。调优时应以 PROFILE 的实际 Rows 为准,对比两者落差,落差越大,越不信任该计划的代价排序。
基数管理:控制行流的关键
基数即算子间流动的行数,同一节点出现在多行时,后续 Expand 会重复执行,导致行数膨胀。管理基数的常用手段包括:使用 DISTINCT 去重、聚合、早 LIMIT(注意写操作后的惰性语义)、变长路径加上界。这些操作能有效削减行流,避免下游算子工作量爆炸。
EXPLAIN 与 PROFILE 的正确用法
EXPLAIN 只展示计划不执行,适合看算子形状;PROFILE 执行查询并返回实际 Rows、DB Hits 等指标,是调优的主要依据。调优时先用 EXPLAIN 看形状,再用 PROFILE 找 Rows 数量级跳变的算子,那往往是事故点。注意 PROFILE 会执行写查询,且冷缓存时结果不可比。
DB Hits 与 Rows 的区分
DB Hits 反映存储层访问次数,Rows 反映实际行数。若 DB Hits 高但 Rows 不高,问题在存储层(如指针追逐、冷页),应回看存储与缓存相关章节;若 Rows 高,则应优先削减基数,而非盲目扩大 page cache。理解两者差异有助于精准定位性能瓶颈。
Q&A
Cypher查询计划中,EXPLAIN和PROFILE有什么区别?
EXPLAIN只展示查询计划而不执行,包含Estimated Rows等估计信息;PROFILE会实际执行查询,并返回Rows、DB Hits等运行时指标。调优时应以PROFILE的实际行数为准。
如何阅读Cypher查询计划表?
计划表自下而上阅读,数据从叶子算子流向根算子。最底行是扫描或Seek,最顶是ProduceResults。关键列包括Estimated Rows(估计行数)、Rows(实际行数)、DB Hits(存储层访问次数)等。
什么是基数(cardinality)?为什么基数管理对Cypher查询性能很重要?
基数是算子之间流动的行数。算子对每一输入行执行并产生新行流,同一节点出现在多行时,后续Expand会重复执行,导致行数膨胀。基数管理(如使用DISTINCT、聚合、早LIMIT)可以控制行数,避免性能问题。
为什么在幂律图上Estimated Rows会低估实际行数,导致查询跑爆?
规划器基于标签计数、索引选择性等统计估计行数,并常假设谓词独立。在幂律图上,平均度数失真,少数超节点度数极高,导致Estimated Rows严重低估实际行数,引发Expand爆炸。
Cypher查询调优时,如何定位性能瓶颈?
使用EXPLAIN看计划形状,用PROFILE找Rows数量级跳变点,对比Estimated与Rows,用DISTINCT/聚合/早LIMIT管理基数,变长路径加上界,统计变更后replan。
当DB Hits高但Rows不高时,问题出在哪里?
DB Hits高但Rows不高时,问题在存储层,如指针追逐、冷页等,应参考存储和page cache相关优化,而不是盲目扩大page cache。
Cypher查询中,使用USING INDEX强制索引有什么风险?
USING INDEX可覆盖规划器选择,但若强迫低基数属性上的Seek,入口Rows本身就大,导致提示成功但执行失败。应以PROFILE的Rows为准,不以是否用了索引为准。
如何避免Cypher查询中的行积(row multiplication)问题?
行积问题常见于第二次MATCH时,对每一行再匹配导致行数膨胀。可以通过collect、pattern comprehension或WITH DISTINCT m先把基数压回去。