我正在使用neo4j
进行图形分析,我想删除给定根节点的所有死组件。
我知道我可以将整个图形加载到内存中,从根节点开始执行bfs
,将所有到达的节点保留在列表中,然后在遍历之后,仅通过保留到达节点。
但我认为这种方法不是内存效率,因为我正在处理数百万个节点。所以,我想知道,neo4j
是否提供了从给定根节点删除无法访问的组件的功能?谢谢!
答案 0 :(得分:3)
不,Neo4j没有直接提供这样的功能。
你可以使用Cypher查询自己完成,但是,它会很昂贵,特别是如果你有一个大图。
最天真的版本是:
MATCH (root), (n)
WHERE id(root) = {rootId}
AND n <> root
AND NOT (n)-[*]-(root)
DETACH DELETE n
如果需要删除大量节点,可能需要引入LIMIT
以确保不会创建可能导致OutOfMemory错误的巨大事务。必须重复查询,直到找不到要删除的内容:
MATCH (root), (n)
WHERE id(root) = {rootId}
AND n <> root
AND NOT (n)-[*]-(root)
WITH n
LIMIT 1000
DETACH DELETE n
RETURN count(n)
无界遍历(-[*]-
)可能(将)导致性能问题。
对于每个节点(全节点扫描),还需要重复遍历,如果大多数节点是,那么这将是昂贵的(至少像 O(n ^ 2))连接到根节点,或者如果未连接的子图也很大。
我碰巧有一个图表可以模拟你的用例。
运行稍微修改过的查询(可变长度关系的上限,因为我的图完全连接):
PROFILE MATCH (root), (n)
WHERE id(root) = 0
AND n <> root
AND NOT (n)-[*..6]-(root)
RETURN count(n);
产生以下执行计划:
+--------------------------+----------------+---------+-----------+---------------------+------------------+
| Operator | Estimated Rows | Rows | DB Hits | Variables | Other |
+--------------------------+----------------+---------+-----------+---------------------+------------------+
| +ProduceResults | 561 | 1 | 0 | count(n) | count(n) |
| | +----------------+---------+-----------+---------------------+------------------+
| +EagerAggregation | 561 | 1 | 0 | count(n) | |
| | +----------------+---------+-----------+---------------------+------------------+
| +AntiSemiApply | 314573 | 1392640 | 0 | n, root | |
| |\ +----------------+---------+-----------+---------------------+------------------+
| | +VarLengthExpand(Into) | 2 | 0 | 378135900 | anon[63] -- n, root | (n)-[:*]->(root) |
| | | +----------------+---------+-----------+---------------------+------------------+
| | +Argument | 1258291 | 1398100 | 0 | n, root | |
| | +----------------+---------+-----------+---------------------+------------------+
| +Filter | 1258291 | 1398100 | 0 | n, root | NOT(n == root) |
| | +----------------+---------+-----------+---------------------+------------------+
| +CartesianProduct | 1398101 | 1398101 | 0 | root -- n | |
| |\ +----------------+---------+-----------+---------------------+------------------+
| | +AllNodesScan | 1398101 | 1398101 | 1398102 | n | |
| | +----------------+---------+-----------+---------------------+------------------+
| +NodeByIdSeek | 1 | 1 | 1 | root | |
+--------------------------+----------------+---------+-----------+---------------------+------------------+
Total database accesses: 379534003
并在 2分20秒(总共1398101个节点)中找到要删除的1392640个节点。
为什么不从根节点一遍遍地遍历,标记连接的节点,并在末尾删除所有无法访问的节点,而不是从每个节点遍历以尝试找到根节点的路径?就像垃圾收集器一样。
作为单个查询呈现(因此WITH count(n) AS dummy
分隔符):
MATCH (n) SET n:Unreachable
WITH count(n) AS dummy
MATCH (root)-[*0..]-(n:Unreachable)
WHERE id(root) = {rootId}
REMOVE n:Unreachable
WITH count(n) AS dummy
MATCH (n:Unreachable)
DETACH DELETE n
请注意从0开始的可变长度关系以匹配根本身并删除其Unreachable
标签。
现在有一个完整的节点扫描,单个遍历(仍然无限制)和标签扫描。如果您知道任意2个节点之间最短路径的最大长度(因为您的模型),您可以设置可变长度关系的上限并限制遍历的路径数。
再一次,在我的图表上运行稍微修改过的查询:
PROFILE MATCH (n)
SET n:Unreachable
WITH count(n) AS dummy
MATCH (root)-[*0..6]-(n:Unreachable)
WHERE id(root) = 0
REMOVE n:Unreachable
WITH count(n) AS dummy
MATCH (n:Unreachable)
RETURN count(n);
产生以下执行计划:
+-----------------------+----------------+---------+---------+----------------------------+-----------------------+
| Operator | Estimated Rows | Rows | DB Hits | Variables | Other |
+-----------------------+----------------+---------+---------+----------------------------+-----------------------+
| +ProduceResults | 8209 | 1 | 0 | count(n) | count(n) |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +EagerAggregation | 8209 | 1 | 0 | count(n) | |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +Apply | 67391877 | 1392640 | 0 | dummy -- n | |
| |\ +----------------+---------+---------+----------------------------+-----------------------+
| | +NodeByLabelScan | 67391877 | 1392640 | 1392641 | n | :Unreachable |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +EagerAggregation | 48 | 1 | 0 | dummy | |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +Eager | | 5461 | 0 | anon[64], dummy, n, root | |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +RemoveLabels | 2342 | 5461 | 5461 | anon[64], dummy, n, root | |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +Filter | 2342 | 5461 | 5461 | anon[64], dummy, n, root | n:Unreachable |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +VarLengthExpand(All) | 2342 | 5461 | 8189 | anon[64], n -- dummy, root | (root)-[:*]->( n@73) |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +Apply | 2342 | 1 | 0 | dummy -- root | |
| |\ +----------------+---------+---------+----------------------------+-----------------------+
| | +NodeByIdSeek | 1182 | 1 | 1 | root | |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +EagerAggregation | 1182 | 1 | 0 | dummy | |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +Eager | | 1398101 | 0 | n | |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +SetLabels | 1398101 | 1398101 | 1398101 | n | |
| | +----------------+---------+---------+----------------------------+-----------------------+
| +AllNodesScan | 1398101 | 1398101 | 1398102 | n | |
+-----------------------+----------------+---------+---------+----------------------------+-----------------------+
Total database accesses: 4207956
并且还会在 4秒中找到要删除的1392640个节点(p!)。
这快了35倍!并且数据库访问减少了90倍!
如果要删除大量节点,则需要将执行拆分为较小的批处理,直到它们无处可执行为止。显然你不能使用单个查询。
准备图表:
MATCH (n) SET n:Unreachable
如果无法在一次传递中执行,则执行此查询,直到它返回0:
MATCH (n)
WHERE NOT n:Unreachable
WITH n
LIMIT 10000
SET n:Unreachable
RETURN count(n)
标记从根目录可到达的节点,再次执行查询,直到它返回0:
MATCH (root)-[*0..]-(n:Unreachable)
WHERE id(root) = {rootId}
WITH n
LIMIT 10000
REMOVE n:Unreachable
RETURN count(n)
当然,因为它必须一次又一次地遍历相同的路径以覆盖连接到根节点的整个子图,所以它将花费比一次通过更长的时间。
删除无法访问的节点,直到没有剩下的节点:
MATCH (n:Unreachable)
WITH n
LIMIT 10000
DETACH DELETE n
RETURN count(n)
实际上,第2步将花费大量时间一遍又一遍地遍历相同的路径,以搜索仍然标有Unreachable
的节点,这样我们就可以得到 O(n ^ 2)算法再次。我们可以改为传播可达节点的“波”,直到我们发现无处可去。
从根节点开始,显然可以访问:
MATCH (root)
WHERE id(root) = {rootId}
SET b:Reachable
REMOVE n:Unreachable
传播“圈内”,直到我们覆盖整个连接的子图:
MATCH (n:Reachable)
WITH n
LIMIT 1000
OPTIONAL MATCH (n)--(u:Unreachable)
SET u:Reachable
REMOVE u:Unreachable
REMOVE n:Reachable
RETURN count(DISTINCT n)
因为我们在扩展wave后立即删除Reachable
标签,所以最后不会有任何节点留下该标签,我们无需清理。因为我们按标签匹配节点并遍历单个关系,所以我们回到 O(n)算法。
以下是示例图表的样子,LIMIT
设置为3而不是1000: