知识库

如何利用连接提示(join hints)避免昂贵的遍历

在使用 Cypher 匹配模式时,需要评估的可能路径数量通常与查询执行时间呈正相关。

如果路径中出现超级节点(即具有大量关系且这些关系类型出现在 MATCH 模式中的节点),或者有足够多的高关系数节点,可能的路径数量会激增,从而拖慢查询速度。通过超级节点的遍历代价很高。

有时在遍历特定模式时,你可以从模型中得知,为了获得最佳性能,两个特定类型节点之间的关系应尽可能沿某一方向遍历,而不是相反方向,这在超级节点的场景中尤为常见。

例如,如果我们有一个表示人们(:People)喜欢音乐艺术家的社交图谱,极有可能其中一些艺术家(甚至很多)是超级节点。假设我们查看 (:Person)-[:LIKES]→(:Artist {name:'Britney Spears'}),我们可以合理假设,一个 :Person 的 :LIKES 关系相对较少,可能不到 100 条,而像 Britney Spears 这样的热门 :Artist 可能会有数百万条指向她的 :LIKES 关系。

通过 Britney Spears 进行遍历代价很高,因为这会将可能路径的数量乘以 :LIKES 关系的数量,导致查询执行成本激增。然而,仅仅遍历 Britney Spears(即只从外部指向她)相对来说成本较低。

当查询可能有多个起始点且规划器未能因超级节点导致的低效遍历而提供高效方案时,你可以在查询中使用连接提示(join hint)来阻止对某节点的遍历。

相反,使用多个起始点,对每个起始点进行扩展,直至到达连接提示中指定的节点。随后使用哈希连接(hash join)来找出从两个方向匹配到的公共节点,进而实现完整的匹配路径。

如果我想找出我的朋友和我共同喜欢的艺术家(以及共同的喜欢次数),我可能会写如下查询:

MATCH (me:Person {name:'Me'})-[:FRIENDS_WITH]-(friend)-[:LIKES]->(a:Artist)<-[:LIKES]-(me)
RETURN a, count(a) as likesInCommon

如果规划器决定仅使用单一起始点来执行此查询,并且未能识别潜在的超级节点问题,它可能会选择通过 :Artist 节点进行扩展,这将非常昂贵。

由于 Cypher 查询规划器能够重新排列相邻的 MATCH 模式(以及跨越某些 WITH 子句),仅仅重新排序模式并不足以解决问题。我可以使用连接提示来确保只遍历目标节点,而不从其内部穿过或离开它。

MATCH (me:Person {name:'Me'})-[:FRIENDS_WITH]-(friend)-[:LIKES]->(a:Artist)<-[:LIKES]-(me)
USING JOIN on a
RETURN a, count(a) as likesInCommon

从查询计划中可以看到,虽然起点相同,但我们会沿两条不同的路径朝向 a,且不会穿过它,并使用节点哈希连接(node hash join)在公共的艺术家节点上统一这两条路径。

© . This site is unofficial and not affiliated with Neo4j, Inc.