Xre*_*res 6 mongodb mongodb-php mongodb-query
我有一个有点复杂的查询,这对我的应用程序非常关键.
$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));
Run Code Online (Sandbox Code Playgroud)
目的是找到前10个帖子,按照他们在desc顺序中的创建时间排序,这些帖子是:
一个).由我自己制作,或b).由我的朋友制作,允许类型为'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));
Run Code Online (Sandbox Code Playgroud)
第一个索引是针对查询条件的第一部分设计的,第二个索引是针对第二个条件设计的,第三个索引是针对第三个条件设计的,并且是多键索引.
一个典型的帖子看起来像这样:
{
"_id": "...",
"owner": "001",
"perm": {
"type": "P",
"list": []
},
"msg": "Nice dress!",
"ca": 1390459269
}
Run Code Online (Sandbox Code Playgroud)
另一个例子:
{
"_id": "...",
"owner": "007",
"perm": {
"type": "C",
"list": ["001", "005"]
},
"msg": "Nice day!",
"ca": 1390837209
}
Run Code Online (Sandbox Code Playgroud)
我知道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))
Run Code Online (Sandbox Code Playgroud)
第一个索引背后的原因是当我有数百万个记录时,查询应该首先从给定的用户ID(朋友)集开始查看,以缩小选择范围.然后它按照记录的反向时间顺序进行检查,以检查每个记录是否具有正确的权限类型.此索引的问题在于查询优化器不知道需要扫描多少条记录才能满足limit(10)条件.它不知道最近的10条记录最终会来自哪里,所以它必须从'$ in'子句中指定的每个id返回最多10条记录,然后为每个'$或'重复相同的事情.因此,如果我有两个'$或'子句,每个子句都包含一个由100个用户ID组成的'$ in',那么它必须扫描足够的记录以匹配来自'$ in'中每个用户的10条记录.第一个'$或',然后来自第二个'$或'的'$ in'中每个用户的10条记录,返回2000条记录(这是解释中返回的n,并且nscanned会高得多)取决于它需要扫描多少记录来查找2000个匹配项,并且根据这2000条记录,所有按时间顺序排列,都需要返回前十名.
那么,如果我按以下顺序构建索引怎么办:"'ca'=> -1,'owner._id'=> 1,'perm.type'=> 1"?嗯,我真的不能那样做,因为当我有数十万用户,有数百万条记录时,大多数记录与观众无关.因此,如果我首先从'ca'=> -1开始,它会在遇到符合条件的记录之前扫描很多不相关的记录,即使它发现的每个命中都将直接计入限制(10),它将会只需要扫描尽可能多的记录,以匹配符合条件的10条记录.但是这次扫描可能是成千上万的记录,甚至更多.最糟糕的是,如果找不到10条记录,则必须通过整个索引来查找.
第二个索引是查看为我指定的每个记录,按相反的时间顺序浏览它,并检查这些记录是否来自我的朋友.这是非常简单的,这里的问题实际上来自于使用它的组合,来自上面的'$ in',limit()和sort(),所有这些都在一个查询中.
在这一点上,我正在研究在应用程序端合并结果的解决方案,但是在应用程序端分解"$ or"很容易,但是如何在条件数组中分解'$ in'( 'owner'=> array('$ in'=> $ friends),'perm.type'=> array('$ in'=> array('P','F')))?
经过3天的测试和研究,导致查询效率低下的原因现在已经清楚了。当前版本(2.6.1)的 MongoDB 仍然无法优化同时使用 $or、$in、limit() 和 sort() 的查询。https://jira.mongodb.org/browse/SERVER-1205和https://jira.mongodb.org/browse/SERVER-3310修复程序仅提高了具有上面列出的 4 个操作中的 3 个操作的查询的性能。当在查询中引入第四个操作时,优化就消失了。即使指定了 limit(10),在 $or 子句中进行完整索引和文档扫描时也会观察到此行为。
通过单独分解 $or 子句并在应用程序端合并结果来解决这个问题的尝试虽然可行,但当我尝试实现分页时遇到了重大障碍。
因此,我当前的解决方案是提出与原始查询等效的查询,同时仅使用 4 个操作中的 3 个。我决定“展平”“$in”运算符,将 $friends 数组中的每个元素转换为另一个“$or”条件,并具有要查询的确切所有者值。因此,我的原始查询中不再有 3 个“$or”条件,而是现在有与 $friends 数组中的元素一样多的“$or”条件,再加上另外 2 个原始“$or”条件。
查询现已优化。当使用explain()运行时,nscannedObjects和nscanned现在已经下降到它们应该的值。考虑有关“$or”的文档说明
当将索引与 $or 查询一起使用时,$or 的每个子句将并行执行。这些子句都可以使用自己的索引。
从性能角度来看,这实际上可能是一个可接受的解决方案。我希望这能帮助任何遇到与我相同问题的人。
| 归档时间: |
|
| 查看次数: |
850 次 |
| 最近记录: |