Neo4j Cypher 复杂查询优化

Posted

技术标签:

【中文标题】Neo4j Cypher 复杂查询优化【英文标题】:Neo4j Cypher complex query optimization 【发布时间】:2021-08-22 15:25:42 【问题描述】:

现在我有一个包含数百万个节点和数百万个边关系的图表。节点之间存在有向关系。

现在假设节点有两个状态A和B。我想找到路径上所有状态A节点没有状态B。

如下图,有节点A--K,然后E、G、J三个是B类型,其余的都是A类型。 图片链接是https://i.stack.imgur.com/a0yOV.jpg

对于节点E,其上下游遍历如下图,所以节点B、H、K不满足要求。

对于节点G,其上下游遍历如下图,所以节点B、D、K不满足要求。

对于节点J,其上下游遍历如下图,所以节点A、B、C、D、F不满足要求。

所以最后只有节点“I”是满足要求的节点。 图片链接是https://i.stack.imgur.com/A2eqv.jpg

上面例子的case是一个DAG,但实际情况是图中可能存在循环,包括自旋循环(案例1)、AB循环(案例2)、大循环(案例3)、复循环(案例4) 图片链接是https://i.stack.imgur.com/NDpED.jpg 我能写的 Cypher 查询语句

MATCH (n:A) 
WHERE NOT exists((n)-[*]->(:B)) 
  AND NOT exists((n)<-[*]-(:B))
RETURN n;

但是这个查询语句在百万节点和百万边的情况下卡住了limit 35,但最终有3万多个节点满足要求。

显然我的语句占用了太多内存,查询出30+个节点几乎已经占用了所有可用内存,如何写一个更高效的查询?

这是一个例子

CREATE (a:Aid:'a') 
CREATE (b:Aid:'b')
CREATE (c:Aid:'c') 
CREATE (d:Aid:'d')
CREATE (e:Bid:'e') 
CREATE (f:Aid:'f')
CREATE (g:Bid:'g') 
CREATE (h:Aid:'h')
CREATE (i:Aid:'i') 
CREATE (j:Bid:'j')
CREATE (k:Aid:'k')
MERGE (a)-[:REF]->(c)  
MERGE (b)-[:REF]->(c)  
MERGE (b)-[:REF]->(d)  
MERGE (b)-[:REF]->(e)
MERGE (c)-[:REF]->(f)  
MERGE (d)-[:REF]->(g)  
MERGE (e)-[:REF]->(g)  
MERGE (e)-[:REF]->(h)
MERGE (f)-[:REF]->(i)  
MERGE (f)-[:REF]->(j)  
MERGE (f)-[:REF]->(k)  
MERGE (g)-[:REF]->(k)
MERGE (g)-[:REF]->(j)

使用此代码将得到结果'i'

MATCH (n:A) 
WHERE NOT exists((n)-[*]->(:B)) 
  AND NOT exists((n)<-[*]-(:B))
RETURN n;

但是当图中有 800,000 个节点(A 类 400,000 个,B 类 400,000 个)和超过 140 万条边时,此代码无法运行结果

【问题讨论】:

【参考方案1】:

一些想法:

我认为这个全局图搜索无法通过单个查询来解决。您将需要某种流程来优化探索并在后续步骤中将结果使用到某个点。 何时可以分配节点标签而不是属性来反映 节点的状态,您可以使用 apoc.path.expandConfig 来 探索路径,直到找到状态为 B 的节点。 在遇到状态 B 的节点之前,您无需重新调查您遍历的状态 A 节点,因为它们不符合要求。

考虑到 B 节点的上行或下行路径上的所有节点都无法满足要求,另一种方法可能是这样。仍然假设您使用标签来区分 A 和 B 节点。

MATCH (b:B)
CALL apoc.path.spanningTree(b,
                           relationshipFilter: "<",
                            labelFilter:"/B"
                           
                           ) YIELD path
UNWIND nodes(path) AS downStreamNode
WITH b,COLLECT(DISTINCT downStreamNode) AS downStreamNodes
CALL apoc.path.spanningTree(b,
                           relationshipFilter: ">",
                            labelFilter:"/B"
                           ) YIELD path
UNWIND nodes(path) AS upStreamNode
WITH b,downStreamNodes+COLLECT(DISTINCT upStreamNode) AS upAndDownStreamNodes
RETURN apoc.coll.toSet(apoc.coll.flatten(COLLECT(upAndDownStreamNodes))) AS allNodesThatDoNotFulfillRequirements

【讨论】:

非常感谢您的回答,我还没有想出如何分步实现这个需求的代码。我目前想用Java实现它的方式是1.将所有环合并到一个节点2.遍历图两次,如果一个节点是A类型,那么它的所有子节点都不满足要求;反转相同 3.统计两次迭代没有标记为不符合的节点个数 这个逻辑能用neo4j实现吗? 我添加了第二种方法,使用生成树。 感谢您的代码!但是当我执行这段代码时,我发现你的代码找到了所有状态为 B 的节点,并没有过滤掉路径上状态为 A 的节点 不会是剩下的那些,不在结果集中的那些 (allNodesThatDoNotFulfillRequirements),是你要找的吗? 我明白你的代码意思是找到所有B类型的节点,但不是所有连接的A类型的节点。“allNodesThatDoNotFulfillRequirements”的意思应该是B类型的所有节点和A类型的所有节点在上游和下游路径上具有类型 B 的节点

以上是关于Neo4j Cypher 复杂查询优化的主要内容,如果未能解决你的问题,请参考以下文章

如何优化 Neo4J Cypher 查询?

如何优化Neo4J Cypher查询?

Neo4j - Cypher vs Gremlin 查询语言

Neo4j 使用cypher语言进行查询

Neo4j 第三篇:Cypher查询入门

Neo4j —— Cypher 查询语言