查询语句写了 limit 1,为什么依然很慢?
本文分享自华为云社区《查询语句写了limit 1,为什么依然很慢? 记一次图引擎GES cypher慢查询的定位分析》,作者: 蜉蝣与海。
问题背景
最近使用华为图引擎 GES, 有一条 cypher 语句携带后缀 limit 1,理论上应该很快返回结果,可是跑了很久依旧没有返回,简化后的语句如下:
调整 limit 10 至 limit 1,结果依旧很慢。
在华为云图引擎GES官网文档上,发现了对查询语句进行终止的接口:
首先使用 listQueries 查询出当前 cypher 语句的 queryId,然后把 queryId 输入 killQuery 中终止慢查询。
问题分析:为什么 limit 配置为 1,依旧长时间无结果?
在华为云图引擎控制台,使用 explain 打印 cypher 查询计划,分析慢查询生成原因,简化后的查询计划如图:
可以看到,查询语句的执行和预期有差异。查询语句优先执行了一个 Label 扫描算子(NodeByLabelScan),对扫描到的每个点按入边方向进行了[r*0..10]这样的 10 跳操作,而后再从出边方向做了一跳并过滤,这就导致了最坏情况下,需要全图 Label 为 Material 的点都跳完 10 跳,才能拿到一条结果。如上图表格所示,在遍历到第一个满足条件的(vi)之前,已经对(v0)到(vi)这些点都做了 10 跳。即使语句末尾写了 limit 1,因为始终遍历不到合适的数据,导致了后台的计算引擎一直在做计算。
估计计算引擎在读取这条语句时,同时看到了 v:Material 和 node.product='xxx'两个过滤条件,而代价估计模型认为 v:Material 可以获得更低的代价,所以才选择优先执行 NodeByLabelScan。
使用语句优化策略对语句进行优化
既然分析清楚了原因,那么只要通过改写 cypher 语句,让 GES 的 cypher 不优先通过 v:Material 做计划即可。这里用到下列两类策略:
with 别名策略:使用 with 为变量安排别名,强制规定语句不同子句的执行顺序
label 过滤改写策略:将 label 过滤条件放在 where 语句中,并使用 labels 函数,防止生成 NodeByLabelScan 的计划
使用 with 别名策略后,语句如下:
使用 label 过滤改写策略,语句如下:
两种策略混合使用,语句如下:
使用 explain 将三种查询计划分别打印,如图:
可以看到 with 别名更有效,生成的计划更符合期望。
使用 with 别名策略改写 cypher 语句后,语句运行时间从 130 秒降到 3 秒。语句中依旧存在 AllNodesScan 算子,而华为图引擎 GES 是支持索引能力的,后续会考虑加入合适的索引,把 AllNodesScan 和 Filter 优化为 NodeIndexSeek,进一步提升语句执行速度。
总结
通过这次分析也可以看出,很多时候计算引擎会对语句进行代价估计并调整语句的执行顺序。执行计划是语句如何执行的直观表达。语句如何执行不能只关注语句写法,要想写出符合预期执行顺序的查询语句,还需要关注语句执行计划。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/92812acfcf9ee5ba9317a8fba】。文章转载请联系作者。
评论