优化Cypher查询Neo4j

3

我希望在Neo4j上写一个Cypher查询并运行它。

查询如下:

给定一些起始顶点,遍历边并找到所有与任何起始顶点相连的顶点。

(start)-[*]->(v)

针对每条被遍历的边E

if startVertex(E).someproperty != endVertex(E).someproperty, output E.

该图可能包含循环。

查询示例

例如,在上面的图中,顶点按“group”属性分组。查询应返回7行,代表图中7个橙色边缘。

如果我自己编写算法,它将是一个简单的深度/广度优先搜索,并且对于访问的每条边,如果过滤条件为真,则输出此边缘。复杂度为O(V+E)。

但是,由于Cypher语言与之很不同,因此我无法用Cypher表达这个算法。

然后我写了这个查询:

查找所有可达的顶点

(start)-[*]->(v), reachable = start + v. 

查找从任何可访问的起始点开始的所有边。如果一条边以任何可达顶点结束并通过筛选器,则输出它。

match (reachable)-[]->(n) where n in reachable and reachable.someprop != n.someprop

因此,Cypher代码如下所示:

MATCH (n:Col {schema:"${DWMDATA}",table:"CHK_P_T80_ASSET_ACCT_AMT_DD"})
WITH n MATCH (n:Col)-[*]->(m:Col)
WITH collect(distinct n) + collect(distinct m) AS c1
UNWIND c1 AS rn
MATCH (rn:Col)-[]->(xn:Col) WHERE rn.schema<>xn.schema and xn in c1
RETURN rn,xn

我认为这个查询的性能并不如我所想。这是在:Col(schema)上建立了索引。

我正在我的Windows笔记本电脑上运行来自DockerHub的neo4j 2.3.0 docker镜像。实际上,它在我的笔记本电脑上的Linux虚拟机上运行。

我的示例数据集是一个包含0.1M个顶点和0.5M个边缘的小数据集。对于一些起始节点,执行此查询需要60秒或更长时间。有什么优化或重写查询的建议吗?谢谢。

以下代码块是我想要的逻辑:

 VertexQueue1 = (starting vertexes);
 VisitedVertexSet = (empty);
 EdgeSet1 = (empty);
 While (VertexSet1 is not empty)
 {
     Vertex0 = VertexQueue1.pop();
     VisitedVertexSet.add(Vertex0);
     foreach (Edge0 starting from Vertex0)
     {
           Vertex1 = endingVertex(Edge0);
           if (Vertex1.schema <> Vertex0.schema)
           {
               EdgeSet1.put(Edge0);
           }
           if (VisitedVertexSet.notContains(Vertex1) 
               and VertexQueue1.notContains(Vertex1)) 
           {
               VertexQueue1.push(Vertex1);
           }
     }
 }
 return EdgeSet1;

编辑:

档案结果显示,展开所有路径的成本很高。从行号来看,似乎Cypher exec引擎返回了所有路径,但我只想要不同的边缘列表。

左边:

match (start:Col {table:"F_XXY_DSMK_ITRPNL_IDX_STAT_W"})
,(start)-[*0..]->(prev:Col)-->(node:Col)
 where prev.schema<>node.schema 
return distinct prev,node

正确的一种:

MATCH (n:Col {schema:"${DWMDATA}",table:"CHK_P_T80_ASSET_ACCT_AMT_DD"})
WITH n MATCH (n:Col)-[*]->(m:Col)
WITH collect(distinct n) + collect(distinct m) AS c1
UNWIND c1 AS rn
MATCH (rn:Col)-[]->(xn:Col) WHERE rn.schema<>xn.schema and xn in c1
RETURN rn,xn

profile result


是的,我认为问题在于使用开放式可变路径时很容易产生探索路径数目的爆炸(顺便说一下,这是不遍历两次路径中的关系,而不是节点)。我认为在Cypher中没有办法停止浏览路径,因为相邻节点的属性不同,除非先加载整个路径然后拒绝它,但不幸的是。你可以考虑直接使用Java API或制作一个未经管理的扩展。 - Brian Underwood
3个回答

2

我认为如果我理解了这个查询,Cypher让这个过程比你预期的要简单得多。尝试这样做:

MATCH (start:Col {schema:"${DWMDATA}",table:"CHK_P_T80_ASSET_ACCT_AMT_DD"})-->(node:Col)
WHERE start.schema <> node.schema
RETURN start, node

虽然我不确定你为什么要比较节点上的schema属性。难道不是通过传递的值来固定起始节点的schema吗?

也许我没有理解查询的意思。如果你想查找与起始节点连接的节点以外的内容,可以执行以下操作:

MATCH
  (start:Col {schema:"${DWMDATA}",table:"CHK_P_T80_ASSET_ACCT_AMT_DD"})
  (start)-[*0..]->(prev:Col)-->(node:Col)
WHERE prev.schema <> node.schema
RETURN prev, node

那种开放式的可变长度关系规范可能会很慢。

此外,请注意,当Cypher浏览特定路径时,它会在发现已经回到路径中之前匹配的某个节点(编辑关系而非节点)时停止,因此循环并不是一个问题。

另外,你传递的DWMDATA值是否已插值?如果是,出于安全性/性能考虑,应考虑使用参数:

http://neo4j.com/docs/stable/cypher-parameters.html

编辑:

根据你的评论,我有一些想法。首先,仅限于DISTINCT path是没有帮助的,因为它找到的每个路径都是唯一的。我想你想要的是一组不同的对,这可以通过将DISTINCT添加到查询中来实现:

MATCH
  (start:Col {schema:"${DWMDATA}",table:"CHK_P_T80_ASSET_ACCT_AMT_DD"})
  (start)-[*0..]->(prev:Col)-->(node:Col)
WHERE prev.schema <> node.schema
RETURN DISTINT prev, node

这里有另一种方法,可能更有效也可能不是最优的,但至少可以让您了解如何以不同的方式进行操作:

MATCH
  path=(start:Col {schema:"${DWMDATA}",table:"CHK_P_T80_ASSET_ACCT_AMT_DD"})-->(node:Col)
WITH rels(path) AS rels
UNWIND rels AS rel
WITH DISTINCT rel
WITH startNode(rel) AS start_node, endNode(rel) AS end_node
WHERE start_node.schema <> end_node.schema
RETURN start_node, end_node

谢谢您的回答 :) 我需要开放式可变长度关系。我运行了您答案中的第二个代码块,并返回了所有可能的起点、前一个节点和节点路径。在我的样本数据(v=0.1M)上,等待300秒后,查询返回了900万行。我认为对于我的数据,期望的输出应该是大约300行(代表300个不同的边缘)。我添加了(prev)->[path]->(node) return distinct path,但它并没有影响到EXPLAIN输出的执行计划。有没有办法告诉neo4j“你只需要访问每个边缘和节点一次”,并加快查询速度? - user2218067
通过添加DISTINCT,查询返回了不同的一组对,但性能并没有提高。似乎执行引擎计算了所有可能的路径,而我并不需要每个不同的路径,只需要不同的边集就可以了。我在问题的最后添加了一个代码块来解释我想要执行的算法(虽然它不是并行的,但我可以改进它)。那么我能调整Cypher执行引擎来执行我描述的算法吗? - user2218067
代码示例很有帮助。又得到了一个答案。 - Brian Underwood

1

我不能保证这种方法会更快,但是这里有另一种尝试的方式:

MATCH (start:Col)-[*]->(node:Col)
WHERE start.property IN {property_values}

WITH collect(ID(node)) AS node_ids

MATCH (:Col)-[r]->(node:Col)
WHERE ID(node) IN node_ids
WITH DISTINCT r
RETURN startNode(r) AS start_node, endNode(r) AS end_node

我怀疑所有情况的问题都出在可变长度路径上。我已经在Slack组上提出了问题,试图更好地理解它的工作原理。同时,建议您在尝试所有查询之前,在查询语句前加上PROFILE关键字,以从Neo4j获得有关查询哪些部分缓慢的报告。


这个查询的PROFILE报告与问题最后一节中的右侧报告相似。看起来引擎在VarLenExpand中访问了每条边超过一次,因此返回的行数很多。想知道是否有任何方法可以告诉引擎按照我的算法描述执行。 - user2218067
为什么要先收集id然后再进行匹配呢?如果这个查询没问题的话,我在回答中添加了一个变量。 - Michael Hunger

1
// this is very inefficient!
MATCH (start:Col)-[*]->(node:Col)
WHERE start.property IN {property_values}
WITH distinct node
MATCH (prev)-[r]->(node)
RETURN distinct prev, node;

你可能更适合使用这个:

MATCH (start:Col)
WHERE start.property IN {property_values}

MATCH (node:Col)
WHERE shortestPath((start)-[*]->(node)) IS NOT NULL

MATCH (prev)-[r]->(node)
RETURN distinct prev, node;

第一个查询在5分钟内完成,但第二个查询在30分钟内没有完成...... neo4j会尝试对每个节点执行最短路径算法吗?如果是这样,我认为这个查询会很慢。 - user2218067

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接