【发布时间】:2016-03-11 11:03:28
【问题描述】:
我正在评估 Neo4j 在生产环境中的使用,在做一些我认为很简单的事情时遇到了一些困难。我已经设法解决了它,但是以一种次优且相当复杂的方式,所以我在想是否有更简单的方法来完成同样的事情。
序言
Neo4j 2.3.2 版
TL;DR
这个解释有点长,总结如下:
给定节点 A,我需要在 A 的子图中找到复杂度为 O(number_of_vertices + number_of_edges) 的所有节点。
问题
对于我们的用例,我们有一个图,它针对一种特定类型的关系被分解成更小的不连贯子图,每个子图不超过几十个节点。我们试图完成的是,给定来自这些节点之一的索引 id 发现子图中的所有节点(同时将图视为无向)。另一个特点是我们的节点总是具有从每个节点返回到自身的自反边。
从算法上讲,我们所需要的只是广度优先搜索,其预期复杂度为 O(num_of_vertices + num_of_edges)。由于我们的图并不密集,因此其中的边数与顶点数大致呈线性关系,因此整体复杂度应与其中的顶点数成线性关系。
测试图
为简单起见,我将此测试图设为全连接图。因为重点是比较密码查询,所以这不会影响结果。
命令:
- CREATE (:Label { id: 1 }),(:Label { id: 2 }),(:Label { id: 3 }),(:Label { id: 4 })
- 匹配 (a),(b) 合并 (a)-[:REL]->(b)
简单查询
我尝试获得所需结果的第一个查询如下:
- MATCH (a:Label {id:1})-[:REL*1..]-(b:Label) RETURN DISTINCT b
该查询从未终止。当我为关系模式添加上限并分析查询时,我得到以下信息:
- 深度 4:3902 db 访问
- 深度 8:1714982 次数据库访问
所以看起来它不是与顶点和边的数量成线性关系,而是在寻找所有可能的路径,这些路径当然会随着深度而爆炸。
性能更好的查询
为此,我编写了以下查询:
https://gist.github.com/Dalamar42/1ec93cd74b01c145e7bd
(这将搜索到深度 2。复制第 6-16 行以搜索到深度 4、6、8 等)
查询执行以下操作:
- 获取入口节点
- 将该节点添加到 nodes_found 集合和另一个 nodes_to_visit 集合中
- 对于 nodes_to_visit 中的每个节点 A,沿其边缘到达节点 B
- 从节点 B 只保留不在 nodes_found 中的节点作为节点 C
- 将 nodes_to_visit 设置为节点 C,并将 nodes_found 设置为其先前的值加上节点 C
- 重复到所需的深度
这个查询应该几乎具有 BFS 的复杂性,除了一个复杂性。据我了解,每个中间 MATCH/WHERE 都需要匹配至少一个节点,否则 cypher 会返回空的节点,而忽略前面步骤中找到的节点。我通过将第 4 步更改为:
"从节点 B 只保留节点 A 和不在 nodes_found 中的节点作为节点 C"
因为所有节点都有自反边,所以节点 A 将始终位于节点 B 的集合中,并且通过始终保留它,我确保查询的该部分将始终匹配至少一个节点。
这意味着这个查询有以下问题:
- 我需要定义最大搜索深度
- 增加深度并不是免费的,因为我每次都在探索 A 的边缘
- 这个查询让人难以阅读,尤其是考虑到这实际上只是一个更大的查询的一部分,需要执行几次
这个查询的好处是我得到了更好的性能
- 用于搜索深度 4:65 db 访问(与“哑”查询中的 3902 相比)
- 用于搜索深度 6:97 db 访问(与“哑”查询中的 1714982 相比)
更好的解决方案? 有谁知道更好/更简单的解决方案?我错过了 Cypher 的一些明显特征吗?我无法通过文档找到任何内容。
谢谢
【问题讨论】: