聚合框架中的$ skip和$ limit

时间:2014-06-11 10:01:02

标签: mongodb aggregation-framework

当我阅读该文件时,我发现了以下注释:

  

当$ sort紧接在管道中的$ limit之前时,$ sort操作仅保持前n个结果,其中n是指定的限制,而MongoDB只需要在内存中存储n个项目。当allowDiskUse为true且n个项超过聚合内存限制时,此优化仍然适用。

如果我对此有正确的话,只有当我一起使用$ sort和$ limit时它才适用

db.coll.aggregate([
    ...,
    {$sort: ...},
    {$limit: limit},
    ...
]);

但是,我认为大部分时间我们都会

db.coll.aggregate([
    ...,
    {$sort: ...},
    {$skip: skip},
    {$limit: limit},
    ...
]);

问题1 :如果我在这里使用$ skip,这是否意味着上述规则不适用?

我问这个问题,因为理论上MongoDB仍然可以计算顶部 n 记录,并通过仅排序顶部 n 记录来提高性能。虽然我没有找到任何关于此的文件。如果规则不适用,

问题2 :我是否需要将查询更改为以下内容以提高效果?

db.coll.aggregate([
    ...,
    {$sort: ...},
    {$limit: skip + limit},
    {$skip: skip},
    {$limit: limit},
    ...
]);

编辑:我认为解释我的用例会让上面的问题变得更有意义。我使用MongoDB 2.6提供的文本搜索功能来查找产品。我担心如果用户输入一个非常常见的关键词,例如" red",则会返回太多结果。因此,我正在寻找更好的方法来产生这种结果。

EDIT2 :事实证明上面的最后一个代码等于

db.coll.aggregate([
    ...,
    {$sort: ...},
    {$limit: skip + limit},
    {$skip: skip},
    ...
]);

因此,我们始终可以使用此表单来应用 top n 规则。

2 个答案:

答案 0 :(得分:43)

由于这是我们所讨论的文本搜索查询,因此最佳形式是:

db.collection.aggregate([
    { "$match": {
        "$text": { "$search": "cake tea" }
    }},
    { "$sort": { "score": { "$meta": "textScore" } },
    { "$limit": skip + limit },
    { "$skip": skip }
])

从顶部"排序"内存储备的基本原理结果只能在其自身的限制范围内发挥作用"事实上,除了几个合理的页面之外,这对于任何事情都不是最佳的。数据。

除了记忆消耗的合理性之外,额外的阶段可能会产生负面影响而不是正面影响。

这些实际上是当前表单中MongoDB可用的文本搜索功能的实际限制。但是对于任何更详细和需要更多性能的东西,就像许多SQL"全文"解决方案,你最好使用外部"专门建造"文本搜索解决方案。

答案 1 :(得分:2)

我发现limitskip的顺序似乎无关紧要。如果我在skip之前指定limit,则mongoDB将在幕后limit之前制作skip

> db.system.profile.find().limit(1).sort( { ts : -1 } ).pretty()
{
    "op" : "command",
    "ns" : "archiprod.userinfos",
    "command" : {
        "aggregate" : "userinfos",
        "pipeline" : [
            {
                "$sort" : {
                    "updatedAt" : -1
                }
            },
            {
                "$limit" : 625
            },
            {
                "$skip" : 600
            }
        ],
    },
    "keysExamined" : 625,
    "docsExamined" : 625,
    "cursorExhausted" : true,
    "numYield" : 4,
    "nreturned" : 25,
    "millis" : 25,
    "planSummary" : "IXSCAN { updatedAt: -1 }",
    /* Some fields are omitted */
}

如果我同时$skip$limit怎么办?我在keysExamineddocsExamined上得到了相同的结果。

> db.system.profile.find().limit(1).sort( { ts : -1 } ).pretty()
{
    "op" : "command",
    "ns" : "archiprod.userinfos",
    "command" : {
        "aggregate" : "userinfos",
        "pipeline" : [
            {
                "$sort" : {
                    "updatedAt" : -1
                }
            },
            {
                "$skip" : 600
            },
            {
                "$limit" : 25
            }
        ],
    },
    "keysExamined" : 625,
    "docsExamined" : 625,
    "cursorExhausted" : true,
    "numYield" : 5,
    "nreturned" : 25,
    "millis" : 71,
    "planSummary" : "IXSCAN { updatedAt: -1 }",
}

然后我检查了查询的解释结果。我发现totalDocsExamined阶段已经625

limit

令人惊讶的是,我发现切换> db.userinfos.explain('executionStats').aggregate([ { "$sort" : { "updatedAt" : -1 } }, { "$limit" : 625 }, { "$skip" : 600 } ]) { "stages" : [ { "$cursor" : { "sort" : { "updatedAt" : -1 }, "limit" : NumberLong(625), "queryPlanner" : { "winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "updatedAt" : -1 }, "indexName" : "updatedAt_-1", } }, }, "executionStats" : { "executionSuccess" : true, "nReturned" : 625, "executionTimeMillis" : 22, "totalKeysExamined" : 625, "totalDocsExamined" : 625, "executionStages" : { "stage" : "FETCH", "nReturned" : 625, "executionTimeMillisEstimate" : 0, "works" : 625, "advanced" : 625, "docsExamined" : 625, "inputStage" : { "stage" : "IXSCAN", "nReturned" : 625, "works" : 625, "advanced" : 625, "keyPattern" : { "updatedAt" : -1 }, "indexName" : "updatedAt_-1", "keysExamined" : 625, } } } } }, { "$skip" : NumberLong(600) } ] } $skip会得到相同的$limit结果。

explain

您可以看到,即使我在> db.userinfos.explain('executionStats').aggregate([ { "$sort" : { "updatedAt" : -1 } }, { "$skip" : 600 }, { "$limit" : 25 } ]) { "stages" : [ { "$cursor" : { "sort" : { "updatedAt" : -1 }, "limit" : NumberLong(625), "queryPlanner" : { /* Omitted */ }, "executionStats" : { "executionSuccess" : true, "nReturned" : 625, "executionTimeMillis" : 31, "totalKeysExamined" : 625, "totalDocsExamined" : 625, /* Omitted */ } } }, { "$skip" : NumberLong(600) } ] } 之前在$skip之前指定了$limit,在explain之前仍然是$limit