在非选择性查询中检查路径不存在的最有效方法是什么?

时间:2015-10-13 15:11:52

标签: graph-databases orientdb

我有一个图形模型,它包含三种类型的顶点(用户,组,文档)和两种类型的边(member_of,permissions)。关系可以表示为:

User,Group --- member_of ---> Group  (depth can be arbitrary)
Group --- permissions ---> Document  (depth is 1)

我正在编写一个查询,回答“所有没有任何文档权限的用户是什么?”。这是一个非选择性的查询,因为我没有为User类指定id。

我想出了这个解决方案:

SELECT id, name FROM User
LET $p = (
    SELECT expand(outE('permissions')) FROM (
        TRAVERSE out('member_of') FROM $parent.$current
    ) 
)
WHERE $p.size() = 0

此解决方案似乎有效,但需要在 12-15秒之间执行。目前在我的图表中,每个都有10,000个用户,组和文档。有大约10,000个权限和~50,000个member_of。

检查路径不存在的最有效方法是什么?有没有办法改善现有查询的性能,还是我采取了错误的方法?

2 个答案:

答案 0 :(得分:1)

有几种方法可以改善您的查询。首先,没有必要扩展Permissions边缘,您可以简单地检查查询中存储的边缘量。我们还可以限制此检查,以便它在具有权限边缘的第一组停止,而不是全部检查(归功于Luigi D给我这个想法)。因此查询变为如下。

SELECT * FROM User
LET $p = (
    SELECT FROM (
        TRAVERSE out('Member_Of') FROM $parent.$current
    ) WHERE out('Permissions').size() > 0 LIMIT 1
)
WHERE $p.size() = 0

如果没有相当大的数据集,我很难检查任何查询改进,但通过使用更明确的out_Member_Of和out_Permissions属性而不是out(字段)函数可能会有一点改进。

可能还有另一个机会通过从遍历结果中“删除”用户记录来略微改进查询,从而减少WHERE子句检查的记录数量。这可以通过

完成
SELECT * FROM User
LET $p = (
    SELECT FROM (
        TRAVERSE out('Member_Of') FROM (SELECT out('Member_Of') FROM $parent.$parent.$current)
    ) WHERE out('Permissions').size() > 0 LIMIT 1
)
WHERE $p.size() = 0

之前的查询也可以重新排列,虽然我怀疑这个查询会因为检查所有遍历的结果而变慢,而不是在第一次停止。这只是你尝试的另一种选择。

SELECT * FROM User
LET $p = (TRAVERSE out('Member_Of') FROM (SELECT out('Member_Of') FROM $parent.$current))
WHERE $p.out('Permissions').size() = 0

现在我将偏离该查询。如果一个组有权访问文档,那么预计算会更快,然后使用预先计算的用户组检查每个用户组。这可以节省大量的重复遍历。

我认为最好的方法是让所有群组都没有文档。这样,所有具有docs的组都可以在遍历其他组之前被删除。

SELECT * FROM (SELECT FROM Group WHERE out('Permissions').size() = 0)
LET $p = (
    SELECT FROM (
        TRAVERSE out('Member_Of') FROM $parent.$current
    ) WHERE out('Permissions').size() > 0 LIMIT 1
)
WHERE $p.size() = 0

也许创建和使用索引会使之前的查询更加高效,尽管这个过程目前看起来有点笨拙。在为out_Permissions创建索引之前,需要使用create property Group.out_Permissions LINKBAG创建属性,然后可以使用CREATE INDEX hasDocument ON Groups (out_Permissions, @rid) notunique METADATA {ignoreNullValues: false}创建索引(以这种方式创建索引似乎很奇怪,但这是唯一的方法我可以让它工作,因此我的评论很棒)。然后,您可以使用select expand(rid) from index:hasDocument where key = null查询索引,这将返回没有权限边缘的所有组,并且将替换上一个查询中的SELECT FROM Group WHERE out('Permissions').size() = 0

所以这里是获取带有docs的组的查询,并检查用户对它的反应。它也正确地返回没有组的用户。

SELECT expand($users)
LET $groups_without_docs = (
  SELECT FROM (SELECT FROM Group WHERE out('Permissions').size() = 0)
  LET $p = (
      SELECT FROM (
          TRAVERSE out('Member_Of') FROM $parent.$current
      ) WHERE out('Permissions').size() > 0 LIMIT 1
  )
  WHERE $p.size() = 0
),
$users = (
  SELECT FROM User 
  LET $groups = (SELECT expand(out('Member_Of')) FROM $current)
  WHERE $groups containsall (@rid in $parent.$groups_without_docs)
)

注意我认为$users = (SELECT FROM User WHERE out('Member_Of') containsall (@rid in $parent.$groups_without_docs))应该有效,但事实并非如此。我认为这可能与我之前发布的错误有关,请参阅https://github.com/orientechnologies/orientdb/issues/4692

我很想知道上面的各种查询是否会改善您的查询,所以请回复。

答案 1 :(得分:0)

正如你所说,这是一个非选择性的查询,所以很难优化。

您是否尝试过向内部查询添加LIMIT?

SELECT id, name FROM User
LET $p = (
    SELECT expand(outE('permissions')) FROM (
        TRAVERSE out('member_of') FROM $parent.$current
    ) LIMIT 1
)
WHERE $p.size() = 0

甚至

SELECT id, name FROM User
LET $p = (
    SELECT sum(outE('permissions').size()) as s FROM (
        TRAVERSE out('member_of') FROM $parent.$current
    ) 
)
WHERE $p[0].s = 0