我的查询有点复杂,对我的应用程序非常关键。
$cur = $col->find(
array (
'$or' => array(
array('owner' => $my_id),
array('owner' => array('$in' => $friends), 'perm.type' => array('$in' => array('P', 'F'))),
array('owner' => array('$in' => $friends), 'perm.list' => $my_id)
)
)
)->limit(10)->skip(0)->sort(array('ca' => -1));
目的是找到前10个帖子,按照desc顺序创建时间排序,这些帖子是:
A)。由我自己制作,或 B)。由我的朋友制作的P' P'对于公众,或者' F'对于朋友,或 C)。由我的朋友制作,许可列表专门指定我作为观众。
变量$ friends是一组用户ID,他们是我的朋友。 perm.type总共有4个值,分别是' P'' F'' S'' C'。 perm.list是一组用户ID,他们有权查看此帖子。
上述查询可用于过滤正确的结果。但是我遇到了在它们上创建有效索引的问题。
我为此查询创建的索引是:
$col->ensureIndex(array('owner' => 1, 'ca' => -1));
$col->ensureIndex(array('owner' => 1, 'perm.type' => 1, 'ca' => -1));
$col->ensureIndex(array('owner' => 1, 'perm.list' => 1, 'ca' => -1));
第一个索引是针对查询条件的第一部分设计的,第二个索引是针对第二个条件设计的,第三个索引是针对第三个条件设计的,并且是一个多键索引。
一个典型的帖子看起来像这样:
{
"_id": "...",
"owner": "001",
"perm": {
"type": "P",
"list": []
},
"msg": "Nice dress!",
"ca": 1390459269
}
另一个例子:
{
"_id": "...",
"owner": "007",
"perm": {
"type": "C",
"list": ["001", "005"]
},
"msg": "Nice day!",
"ca": 1390837209
}
我知道MongoDB 2.6之前存在的限制,它可以防止在组合$或sort()时使用索引。根据此http://jira.mongodb.org/browse/SERVER-1205的问题应该在2.6中修复。
果然,explain()现在显示了我的索引的使用,它在2.4之前没有。但是当我运行查询时,它现在比它没有使用任何索引要慢得多。 explain()显示nscanned高于预期。经过一番搜索,我发现这个问题https://jira.mongodb.org/browse/SERVER-3310似乎解释了我遇到的问题。但是,正如票证所述,这个问题应该在2.5.5中修复,那么是什么导致了我的问题呢?
我尝试设置不同的索引,将它们按不同的顺序复合,甚至将它们分开,检查新的索引交集功能是否有用。但都没有效果。
有谁知道我的问题是什么?
修改 经过更多的测试,观察和思考,我已经缩小了问题的范围,它实际上是在导致问题的一个查询中使用$ in,limit()和sort()。添加顶级' $或'为每个' $或'加倍解决这个问题。条款。我将在下面解释我的逻辑:
我已将我的索引改进为以下内容:
$col->ensureIndex(array('owner._id' => 1, 'ca' => -1, 'perm.type' => 1));
$col->ensureIndex(array('perm.list' => 1, 'ca' => -1, 'owner._id' => 1))
第一个索引背后的原因是当我有数百万个记录时,查询应该首先从给定的用户ID(朋友)集开始查看,以缩小选择范围。然后它按照记录的反向时间顺序进行检查,以检查每个记录是否具有正确的权限类型。此索引的问题在于查询优化器不知道需要扫描多少条记录才能满足limit(10)条件。它不知道最近的10条记录最终会来自哪里,所以它必须从' $ in'中指定的每个ID返回最多10条记录。条款,然后为每个' $或'重复相同的事情。所以,如果我有两个' $或'条款,每个条款都有一个' $ in'由100个用户ID组成,然后它必须扫描足够的记录以匹配来自' $ in'中每个用户的10条记录。第一个' $或',然后和来自' $ in'中的每个用户的10条记录第二个' $或',返回2000条记录(这是解释中返回的n,并且nscanned会更高,具体取决于扫描找到2000条匹配所需的记录数),从这2000条记录开始,所有按时间顺序排列,都需要排在前十位。
那么,如果我按以下顺序构建索引怎么办?"' ca' => -1,' owner._id' => 1,' perm.type' => 1&#34 ;?好吧,我无法做到这一点,因为当我拥有数十万用户,拥有数百万条记录时,大多数记录都与观众无关。所以,如果我从' ca' => -1首先,它会在击中符合条件的记录之前扫描很多不相关的记录,即使它找到的每个命中都会直接计入限制(10),并且只需扫描所需数量的记录匹配10条符合标准的记录。但是这次扫描可能是成千上万的记录,甚至更多。最糟糕的是,如果找不到10条记录,则必须通过整个索引才能找到。
第二个索引是查看为我指定的每个记录,按相反的时间顺序查看,并查看这些记录是否来自我的朋友。这是非常简单的,这里的问题实际上来自于使用它,与上面的' $ in,limit()和sort(),在一个查询中一起使用。
此时,我正在研究在应用程序方面合并结果的解决方案,但是分解了“$”或“'在应用程序方面做很容易,但我如何分解' $ in'在条件数组中('所有者' =>数组(' $ in' => $ friends),' perm.type' =>数组(& #39; $ in' =>数组(' P',' F')))?
答案 0 :(得分:1)
我不确定这是否是MongoDB 2.6中的错误,但您可以查看有关索引创建的this article。
索引中字段的顺序应为:
1. First, fields on which you will query for exact values. 2. Second, fields on which you will sort. 3. Finally, fields on which you will query for a range of values.
因此,根据该建议,您可以尝试使用这些索引:
$col->ensureIndex(array('owner' => 1, 'ca' => -1));
$col->ensureIndex(array('ca' => -1, 'owner' => 1, 'perm.type' => 1));
$col->ensureIndex(array('perm.list' => 1, 'ca' => -1, 'owner' => 1));
修改强>
从您的解释来看,如果您在小型数据集上进行测试,那么完全收集很快,因为MongoDB不需要经历大量文档。您应该尝试使用例如10000个文档进行测试以查看真正的差异。索引中字段的值应该足够不同,以确保查询的索引选择性(例如,并非所有文档都来自同一所有者)。
答案 1 :(得分:0)
TL; DR:我相信您使用错误的算法/数据结构,反之亦然。我建议使用扇出方法discussed in this SO question或my blog post。很抱歉无耻地宣传我以前的帖子,但在这里重复这些信息是没有意义的。
与典型的SQL哲学相反,MongoDB的理念是写重_ 。您实际上是在MongoDB查询中实现排名算法,但MongoDB的查询原则是“逐个查询”。这不太适合。
当然,聚合管道不再符合这种理念,事情可能会改变。在允许更复杂查询的方式上有优化,例如索引交集。
但是,你在这里做的事情很难控制。您不仅希望MongoDB使用索引交集(2.6中的新增功能,仅适用于当前的两个索引),而且您还要将它与$in
个查询和复合指数。这有很多问题,如果$in
中的朋友数量增长太多,那么无论如何你都会运气不好。如果一条新闻与太多人共享,情况也是如此,最糟糕的情况是文档增长超过16MB。不断增长的文档很昂贵,复杂的查询很昂贵,大文档也很昂贵。
我建议您在新闻源中使用扇出方法,在代码中实现非常复杂的排名算法,而不是在MongoDB中。
我并不是说优化你的查询是不可能的,但是因为explain
的输出非常多,并且有很多效果在这里相互作用(典型的数组大小,典型的匹配率,索引的选择性,即使对于完全访问数据的人(即你),也很难找到解决这个问题的好方法。
即使你让它工作,如果访问模式发生变化,数据发生变化等,你可能会遇到严重的问题,所以你将处理一个脆弱的结构。
答案 2 :(得分:0)
经过3天的测试和研究,导致低效查询的原因现在已经很清楚了。当前版本(2.6.1)的MongoDB仍然无法一次性优化使用$或$ in,limit()和sort()的查询。 https://jira.mongodb.org/browse/SERVER-1205和https://jira.mongodb.org/browse/SERVER-3310修复程序,每个修复程序仅提高了上面列出的4个操作中的3个查询的性能。在查询中引入第4个操作时,优化就会消失。即使指定了limit(10),也会在$或子句中的完整索引和文档扫描中观察到此行为。
在我尝试实现分页时,通过单独分解$或子句并在应用程序端合并结果来解决此问题的尝试遇到了主要障碍。
我当前的解决方案是提出与原始查询的等效查询,而只使用4个操作中的3个。我决定“压扁”#39; ' $ in'运算符,将$ friends数组中的每个元素转换为另一个元素' $或'具有要查询的确切所有者值的条件。所以不要让3' $或'在我原始查询中的条件,我现在拥有尽可能多的' $或'条件,因为我在我的$ friends数组中有元素,加上另外2个原始' $或'条件。
现在优化了查询。当使用explain()运行时,nscannedObjects和nscanned现在已经向下,它们被认为是值。考虑关于' $或'的文档。陈述
当使用带有$或查询的索引时,$或will的每个子句 并行执行。这些子句都可以使用自己的索引。
这实际上可能是性能方面可接受的解决方案。 我希望这会帮助遇到同样问题的人。