Neo4j 密码没有循环,没有双路径
我目前正在建模一个数据库,其中包含超过50000个节点,每个节点都有2个定向关系。我尝试获取一个输入节点(根节点)的所有节点,这些节点通过一个关系和这些节点的所有所谓子节点连接到它,以此类推,直到到达每个直接或间接连接到此根节点的节点Neo4j 密码没有循环,没有双路径,neo4j,cypher,Neo4j,Cypher,我目前正在建模一个数据库,其中包含超过50000个节点,每个节点都有2个定向关系。我尝试获取一个输入节点(根节点)的所有节点,这些节点通过一个关系和这些节点的所有所谓子节点连接到它,以此类推,直到到达每个直接或间接连接到此根节点的节点 String query = "MATCH (m {title:{title},namespaceID:{namespaceID}})-[:categorieLinkTo*..]->(n) " + "RETURN DISTINCT n.title A
String query =
"MATCH (m {title:{title},namespaceID:{namespaceID}})-[:categorieLinkTo*..]->(n) " +
"RETURN DISTINCT n.title AS Title, n.namespaceID " +
"ORDER BY n.title";
Result result = db.execute(query, params);
String infos = result.resultAsString();
我已经读到运行时更可能是O(n^x),但我找不到任何排除循环或一个节点的多条路径的命令,因此查询需要2个多小时,这对于我的用例来说是不可接受的。对于简单关系表达式,Cypher通过强制执行以下命令自动排除多个关系: 在模式匹配时,Neo4j确保不包括在单个模式中多次发现相同图形关系的匹配 关于这是否适用于可变长度路径,文档并不完全清楚——因此,让我们设计一个小实验来证实这一点:
CREATE
(n1:Node {name: "n1"}),
(n2:Node {name: "n2"}),
(n3:Node {name: "n3"}),
(n4:Node {name: "n4"}),
(n1)-[:REL]->(n2),
(n2)-[:REL]->(n3),
(n3)-[:REL]->(n2),
(n2)-[:REL]->(n4)
这将导致以下图表:
查询:
MATCH (n:Node {name:"n1"})-[:REL*..]->(m)
RETURN m
结果是:
╒══════════╕
│m │
╞══════════╡
│{name: n2}│
├──────────┤
│{name: n3}│
├──────────┤
│{name: n2}│
├──────────┤
│{name: n4}│
├──────────┤
│{name: n4}│
└──────────┘
╒══════════╕
│m │
╞══════════╡
│{name: n2}│
├──────────┤
│{name: n3}│
├──────────┤
│{name: n4}│
└──────────┘
如您所见,n4
被多次包含(因为它可以通过避免循环和穿过循环来访问)。
使用配置文件检查执行情况
:
因此,我们应该使用DISTINCT
来消除重复项:
MATCH (n:Node {name:"n1"})-[:REL*..]->(m)
RETURN DISTINCT m
结果是:
╒══════════╕
│m │
╞══════════╡
│{name: n2}│
├──────────┤
│{name: n3}│
├──────────┤
│{name: n2}│
├──────────┤
│{name: n4}│
├──────────┤
│{name: n4}│
└──────────┘
╒══════════╕
│m │
╞══════════╡
│{name: n2}│
├──────────┤
│{name: n3}│
├──────────┤
│{name: n4}│
└──────────┘
再次使用PROFILE
检查执行情况:
对于简单关系表达式,Cypher通过强制执行以下操作自动排除多个关系: 在模式匹配时,Neo4j确保不包括在单个模式中多次发现相同图形关系的匹配 关于这是否适用于可变长度路径,文档并不完全清楚——因此,让我们设计一个小实验来证实这一点:
CREATE
(n1:Node {name: "n1"}),
(n2:Node {name: "n2"}),
(n3:Node {name: "n3"}),
(n4:Node {name: "n4"}),
(n1)-[:REL]->(n2),
(n2)-[:REL]->(n3),
(n3)-[:REL]->(n2),
(n2)-[:REL]->(n4)
这将导致以下图表:
查询:
MATCH (n:Node {name:"n1"})-[:REL*..]->(m)
RETURN m
结果是:
╒══════════╕
│m │
╞══════════╡
│{name: n2}│
├──────────┤
│{name: n3}│
├──────────┤
│{name: n2}│
├──────────┤
│{name: n4}│
├──────────┤
│{name: n4}│
└──────────┘
╒══════════╕
│m │
╞══════════╡
│{name: n2}│
├──────────┤
│{name: n3}│
├──────────┤
│{name: n4}│
└──────────┘
如您所见,n4
被多次包含(因为它可以通过避免循环和穿过循环来访问)。
使用配置文件检查执行情况
:
因此,我们应该使用DISTINCT
来消除重复项:
MATCH (n:Node {name:"n1"})-[:REL*..]->(m)
RETURN DISTINCT m
结果是:
╒══════════╕
│m │
╞══════════╡
│{name: n2}│
├──────────┤
│{name: n3}│
├──────────┤
│{name: n2}│
├──────────┤
│{name: n4}│
├──────────┤
│{name: n4}│
└──────────┘
╒══════════╕
│m │
╞══════════╡
│{name: n2}│
├──────────┤
│{name: n3}│
├──────────┤
│{name: n4}│
└──────────┘
再次使用PROFILE
检查执行情况:
我们当然可以做一些事情来改进这个查询 首先,您根本不使用标签。而且,由于您没有使用标签,输入节点上的匹配无法利用现有的任何架构索引,它必须扫描访问和比较属性的所有50k节点,直到找到具有给定标题和命名空间的每个节点(当找到一个节点时,它不会停止,因为它不知道是否有其他节点满足条件)。您可以通过仅在开始节点上进行匹配来检查计时 为了改进这一点,应该为节点添加标签,并且开始节点上的匹配项应该包含标签,并且应该为标题和名称空间ID属性编制索引 仅此一项就可以显著提高查询速度 下一个问题是,剩余的瓶颈是由于排序,还是由于返回了大量的结果集 您可以通过限制返回的结果来单独检查排序的成本 匹配后,您可以在查询结束时使用此选项
WITH DISTINCT n
ORDER BY n.title
LIMIT 10
RETURN n.title AS Title, n.namespaceID
ORDER BY n.title
此外,在执行任何性能调整时,您应该分析您的查询(至少是那些在合理时间内完成的查询),并解释那些花了大量时间来检查查询计划的查询。我们当然可以做一些事情来改进此查询
public static HashSet<Node> breadthFirst(String name, int namespace, GraphDatabaseService db) {
// Hashmap for storing the cypher query and its parameters
Map<String, Object> params = new HashMap<>();
// Adding the title and namespaceID as parameters to the Hashmap
params.put("title", name);
params.put("namespaceID", namespace);
/*it is a simple BFS with these variables below
* basically a Queue (touched) as usual, a Set to store the nodes
* which have been used (finished), a return variable and 2 result
* variables for the queries
*/
Node startNode = null;
String query = "Match (n{title:{title},namespaceID:{namespaceID}})-[:categorieLinkTo]-> (m) RETURN m";
Queue<Node> touched = new LinkedList<Node>();
HashSet<Node>finished = new HashSet<Node>();
HashSet<Node> returnResult = new HashSet<Node>();
Result iniResult = null;
Result tempResult=null;
/*the part below get the direct nodes and puts them
* into the queue
*/
try (Transaction tx = db.beginTx()) {
iniResult =db.execute(query,params);
while(iniResult.hasNext()){
Map<String,Object> iniNode=iniResult.next();
startNode=(Node) iniNode.get("m");
touched.add(startNode);
finished.add(startNode);
}
tx.success();
}catch (QueryExecutionException e) {
logger.error("Fehler bei Ausführung der Anfrage", e);
}
/*and now we just execute the BFS (don't think i need more to
* say here.. we are all pros ;))
* as usual, marking every node we have visited
* and saving every visited node.
* the difficult part had been the casting from
* and to node and result, everything else is pretty much
* straightforward. I think the variables explain their self
* via their name....
*/
while(! (touched.isEmpty())){
try (Transaction tx = db.beginTx()) {
Node currNode=touched.poll();
returnResult.add(currNode);
tempResult=null;
Map<String, Object> paramsTemp = new HashMap<>();
paramsTemp.put("title",currNode.getProperty("title").toString());
paramsTemp.put("namespaceID", 14);
String tempQuery = "MATCH (n{title:{title},namespaceID:{namespaceID}})-[:categorieLinkTo] -> (m) RETURN m";
tempResult = db.execute(tempQuery,paramsTemp);
while(tempResult.hasNext()){
Map<String, Object> currResult= null;
currResult=tempResult.next();
Node tempCurrNode = (Node) currResult.get("m");
if (!finished.contains(tempCurrNode)){
touched.add(tempCurrNode);
finished.add(tempCurrNode);
}
}
tx.success();
}catch (QueryExecutionException f) {
logger.error("Fehler bei Ausführung der Anfrage", f);
}
}
return returnResult;
}
首先,您根本没有使用标签。而且,由于您没有使用标签,输入节点上的匹配无法利用现有的任何架构索引,它必须扫描访问和比较属性的所有50k节点,直到找到具有给定标题和命名空间的每个节点(当找到一个节点时,它不会停止,因为它不知道是否有其他节点满足条件)。您可以通过仅在开始节点上进行匹配来检查计时
为了改进这一点,应该为节点添加标签,并且开始节点上的匹配项应该包含标签,并且应该为标题和名称空间ID属性编制索引
仅此一项就可以显著提高查询速度
下一个问题是,剩余的瓶颈是由于排序,还是由于返回了大量的结果集
您可以通过限制返回的结果来单独检查排序的成本
匹配后,您可以在查询结束时使用此选项
WITH DISTINCT n
ORDER BY n.title
LIMIT 10
RETURN n.title AS Title, n.namespaceID
ORDER BY n.title
此外,在执行任何性能调优时,您应该分析查询(至少是在合理时间内完成的查询),并解释那些花费了大量时间来检查查询计划的查询。公共静态哈希集breadthFirst(字符串名、int名称空间、GraphDatabaseService db){
public static HashSet<Node> breadthFirst(String name, int namespace, GraphDatabaseService db) {
// Hashmap for storing the cypher query and its parameters
Map<String, Object> params = new HashMap<>();
// Adding the title and namespaceID as parameters to the Hashmap
params.put("title", name);
params.put("namespaceID", namespace);
/*it is a simple BFS with these variables below
* basically a Queue (touched) as usual, a Set to store the nodes
* which have been used (finished), a return variable and 2 result
* variables for the queries
*/
Node startNode = null;
String query = "Match (n{title:{title},namespaceID:{namespaceID}})-[:categorieLinkTo]-> (m) RETURN m";
Queue<Node> touched = new LinkedList<Node>();
HashSet<Node>finished = new HashSet<Node>();
HashSet<Node> returnResult = new HashSet<Node>();
Result iniResult = null;
Result tempResult=null;
/*the part below get the direct nodes and puts them
* into the queue
*/
try (Transaction tx = db.beginTx()) {
iniResult =db.execute(query,params);
while(iniResult.hasNext()){
Map<String,Object> iniNode=iniResult.next();
startNode=(Node) iniNode.get("m");
touched.add(startNode);
finished.add(startNode);
}
tx.success();
}catch (QueryExecutionException e) {
logger.error("Fehler bei Ausführung der Anfrage", e);
}
/*and now we just execute the BFS (don't think i need more to
* say here.. we are all pros ;))
* as usual, marking every node we have visited
* and saving every visited node.
* the difficult part had been the casting from
* and to node and result, everything else is pretty much
* straightforward. I think the variables explain their self
* via their name....
*/
while(! (touched.isEmpty())){
try (Transaction tx = db.beginTx()) {
Node currNode=touched.poll();
returnResult.add(currNode);
tempResult=null;
Map<String, Object> paramsTemp = new HashMap<>();
paramsTemp.put("title",currNode.getProperty("title").toString());
paramsTemp.put("namespaceID", 14);
String tempQuery = "MATCH (n{title:{title},namespaceID:{namespaceID}})-[:categorieLinkTo] -> (m) RETURN m";
tempResult = db.execute(tempQuery,paramsTemp);
while(tempResult.hasNext()){
Map<String, Object> currResult= null;
currResult=tempResult.next();
Node tempCurrNode = (Node) currResult.get("m");
if (!finished.contains(tempCurrNode)){
touched.add(tempCurrNode);
finished.add(tempCurrNode);
}
}
tx.success();
}catch (QueryExecutionException f) {
logger.error("Fehler bei Ausführung der Anfrage", f);
}
}
return returnResult;
}
//用于存储cypher查询及其参数的Hashmap
Map params=新的HashMap();
//将标题和命名空间ID作为参数添加到Hashmap
参数put(“标题”,名称);
参数put(“namespaceID”,名称空间);
/*这是一个具有以下变量的简单BFS
*基本上是一个队列(触摸式),一个用于存储节点的集合
*已使用(已完成)、一个返回变量和2个结果
*查询的变量
*/
节点startNode=null;
String query=“匹配(n{title:{title},namespaceID:{namespaceID}})-[:categorieLinkTo]->(m)返回m”;
Queue toucted=新建LinkedList();
HashSetfinished=新的HashSet();
HashSet returnResult=新HashSet();
结果INIRESS=null;
结果tempResult=null;
/*下面的部分获取直接节点并将