【问题标题】:Can I do a second 'query' on a MongoDB cursor?我可以对 MongoDB 游标进行第二次“查询”吗?
【发布时间】:2015-07-31 13:14:34
【问题描述】:

想象一个包含大约 5,000,000 个文档的集合。我需要做一个basicCursor 查询来根据太多要索引的字段来选择~100 个文档。我们称之为basicCursorMatch。这将非常缓慢。

但是,我可以对一些索引进行bTreeCursor 查询,这会将我的搜索限制为大约 500 个文档。我们将此查询称为bTreeCursorMatch

有没有办法可以直接在 bTreeCursorMatch 产生的光标或集合上执行此操作 basicCursorMatch

我凭直觉尝试过

var cursor = collection.find(bTreeCursorMatch);
var results = cursor.find(basicCursorMatch);

类似于collection.find(bTreeCursorMatch).find(basicCursorMatch),似乎不起作用。

或者,我希望我能做这样的事情:

collection.aggregate([
    {$match: bTreeCursorMatch}, // Uses index 5,000,000 -> 500 fast
    {$match: basicCursorMatch}, // No index, 500 -> 100 'slow'
    {$sort}
]);

.. 但似乎我也不能这样做。有没有其他方法可以做我想做的事?

我问的原因是因为第二个查询会有很大不同,而且我无法索引所有字段。但我确实想使用bTreeCursor 进行第一次查询,否则使用basicCursor 查询整个集合将永远需要。

更新

此外,通过用户输入,500 个文档的子选择将在会话期间以不同方式查询,其中包含不可预测的 basicCursor 查询,使用多个 $in $eq $gt $lt。但在此期间,bTreeCursor 子选择保持不变。我应该对每个用户查询都继续进行这两个查询,还是有更有效的方法来为这个集合保留reference

【问题讨论】:

  • 您不能连接查询,因为游标“活”在数据库中,它们已经包含查询和游标位置。但是您可以简单地在查询末尾添加附加条件,或强制使用索引。
  • 用两个 $match 元素定义一个聚合管道是可行的。您收到什么错误消息?
  • @Pascal Bugnion 认真的吗?那我一定是做错了什么。我很快会再试一次。这会使用索引进行第一个查询吗?还是只是合并查询并使用基本游标?我使用的是 MongoDB 2.4,也许这两个 $matches 需要 3.0
  • 如果可以,它将使用索引。如果您将 { explain : true } 作为第二个参数传递给聚合调用,它将返回它将如何进行查询,而不是实际执行查询。您会发现,对于每个州,都有一个包含“光标”信息的“计划”字段。
  • 也就是说,我认为您假设 MongoDB 引擎不如实际聪明。如果您将查询传递给find,其中一些字段有索引而其他字段没有,它将首先使用适当的索引进行子选择,然后对剩余的文档进行全面搜索。

标签: mongodb indexing cursor aggregation-framework


【解决方案1】:

实际上,您很少需要在游标上运行第二个查询。您特别不需要将 MongoDB 的工作分解为单独的可索引/不可索引块。

如果您将查询传递给 MongoDB 的 find 方法,该方法可以通过在索引中查找部分完成,MongoDB 将首先进行查找,然后对剩余的文档进行完整扫描。

例如,我有一个集合users,其中包含以下文档:

{ _id : 4, gender : "M", ... }

_id 上有索引,但没有性别索引。 users 中有大约 2 亿个文档。

要了解 MongoDB 在后台执行的操作,请将 explain() 添加到光标(在 Mongo shell 中):

> db.users.find( { _id : { $gte : 1, $lt : 10 } } ).explain()
{
    "cursor" : "BtreeCursor oldId_1_state_1",
    "n" : 9,
    "nscannedObjects" : 9
}

我已经删除了explain 返回的一些字段。基本上,游标告诉您它是否使用索引,n 告诉您查询返回的文档数,nscannedObjects 是查询期间扫描的对象数。在这种情况下,mongodb 能够准确扫描正确数量的对象。

如果我们现在也查询性别会发生什么?

> db.users.find( { _id : { $gte : 1, $lt : 10 }, gender : "F" } ).explain()
{
    "cursor" : "BtreeCursor oldId_1_state_1",
    "n" : 5,
    "nscannedObjects" : 9
}

find 返回 5 个对象,但必须扫描 9 个文档。因此,它能够使用 _id 字段隔离正确的 9 个文档。然后它遍历所有 9 个文档并按性别过滤它们。

【讨论】:

  • 谢谢。我现在已经赞成这个答案(和以前的 cmets)。但是我不确定这个答案是否足够先进。我会为增加一点复杂性的努力鼓掌。例如,实际上我经常发现explain 返回 3 或 5 个具有不同索引的计划和游标。我已经使用基本解释来验证我的查询,但是多个计划是令人困惑的地方。此外,未解决对文档进行半永久性子选择的工作(请参阅编辑)。例如。您是否会简单地将每个“子查询”的两个查询对象合并?
  • 你的评论让我有点困惑。在find 上调用explain 会返回一个计划。如果您运行db.collection.find().explain({verbose: true}),您将获得一个名为allPlans 的额外子文档,其中包含可能已应用于查询的所有计划。使用的计划是解释文档顶层的计划。
  • 当您使用{ explain : true} 运行聚合时,您将获得聚合管道中每个阶段的计划(MongoDB 还添加了 allPlans 子文档,其中列出了为该阶段考虑的所有计划,但顶层计划是实际执行的计划)。
  • 想象一下第一个 $match 花了第二个。然后想象你想做一个$group 来计算所有文档的字段。接下来,您想将$match$group 分开。这是您需要进行第二次查询的地方,因为两个管道共享相同的初始查询。理想情况下,您希望在第一个 $match 上并行执行 $group 和第二个独立 $match 以确保一秒钟的安全。但是这是不可能的,所以我们将整个初始匹配进行了两次。
猜你喜欢
  • 1970-01-01
  • 2018-10-18
  • 2021-11-02
  • 2013-05-10
  • 1970-01-01
  • 1970-01-01
  • 2019-07-02
  • 2014-10-01
  • 2012-02-03
相关资源
最近更新 更多