优化 Cypher 查询 Neo4j
Optimizing Cypher Query Neo4j
我想在 Cypher 中编写一个查询,运行 它在 Neo4j 中。
查询是:
给定一些起始顶点,遍历边并找到连接到任何起始顶点的所有顶点。
(start)-[*]->(v)
E 走过的每条边
if startVertex(E).someproperty != endVertex(E).someproperty, output E.
该图可能包含循环。
例如,在上图中,顶点按 "group" 属性 分组。查询应该 return 7 行代表图中的 7 条橙色边。
如果我自己写算法就是一个简单的深度/广度优先搜索,如果过滤条件为真,则对于访问的每条边,输出这条边。复杂度为 O(V+E)
但是我无法用 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)
上有索引
我 运行ning neo4j 2.3.0 docker 图片来自 dockerhub 在我的 windows 笔记本电脑上。实际上它 运行 在我笔记本电脑上的 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 引擎 return 是所有路径,但我只想要不同的边缘列表。
剩下一个:
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
我认为 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 浏览特定路径时,它会停止并发现它已循环回到某个节点(EDIT relationship , 到目前为止匹配的路径中没有 node),所以循环不是真正的问题。
此外,您传入的 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
我不能说这样会更快,但可以尝试另一种方法:
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 获取有关查询的哪些部分速度较慢的报告。
// 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;
我想在 Cypher 中编写一个查询,运行 它在 Neo4j 中。
查询是:
给定一些起始顶点,遍历边并找到连接到任何起始顶点的所有顶点。
(start)-[*]->(v)
E 走过的每条边
if startVertex(E).someproperty != endVertex(E).someproperty, output E.
该图可能包含循环。
例如,在上图中,顶点按 "group" 属性 分组。查询应该 return 7 行代表图中的 7 条橙色边。
如果我自己写算法就是一个简单的深度/广度优先搜索,如果过滤条件为真,则对于访问的每条边,输出这条边。复杂度为 O(V+E)
但是我无法用 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)
上有索引我 运行ning neo4j 2.3.0 docker 图片来自 dockerhub 在我的 windows 笔记本电脑上。实际上它 运行 在我笔记本电脑上的 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 引擎 return 是所有路径,但我只想要不同的边缘列表。
剩下一个:
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
我认为 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 浏览特定路径时,它会停止并发现它已循环回到某个节点(EDIT relationship , 到目前为止匹配的路径中没有 node),所以循环不是真正的问题。
此外,您传入的 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
我不能说这样会更快,但可以尝试另一种方法:
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 获取有关查询的哪些部分速度较慢的报告。
// 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;