聚合框架中的$ skip和$ limit

yao*_*ing 38 mongodb aggregation-framework

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

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

如果我对此是正确的,它仅适用于我同时使用$ sort和$ limit的情况

db.coll.aggregate([
    ...,
    {$sort: ...},
    {$limit: limit},
    ...
]);
Run Code Online (Sandbox Code Playgroud)

但是,我想我们大部分时间都会这样做

db.coll.aggregate([
    ...,
    {$sort: ...},
    {$skip: skip},
    {$limit: limit},
    ...
]);
Run Code Online (Sandbox Code Playgroud)

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

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

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

db.coll.aggregate([
    ...,
    {$sort: ...},
    {$limit: skip + limit},
    {$skip: skip},
    {$limit: limit},
    ...
]);
Run Code Online (Sandbox Code Playgroud)

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

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

db.coll.aggregate([
    ...,
    {$sort: ...},
    {$limit: skip + limit},
    {$skip: skip},
    ...
]);
Run Code Online (Sandbox Code Playgroud)

因此,我总是可以使用此表单来应用前n个规则.

Nei*_*unn 53

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

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

从顶级"排序"结果中记忆储备的基本原理只能在它自己的"限制"范围内工作,并且这对于除了几个合理的"页面"数据之外的任何东西都不是最佳的.

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

这些实际上是当前表单中MongoDB可用的文本搜索功能的实际限制.但是对于任何更详细和需要更高性能的内容,就像许多SQL"全文"解决方案一样,您最好使用外部"专用"文本搜索解决方案.


Tre*_*eru 15

答案:在$limit之前$skip

$skip 和 $limit 的顺序绝对重要,至少对于聚合而言。我刚刚尝试过这个,我不知道它是如何被错过的,也许自操作以来它已经改变了,但我想我会分享。

我同意 @vkarpov15在这次对话中的评论

总的来说,$limit 限制发送到下一个聚合状态的文档数量,而 $skip 会跳过前 N 个文档,因此如果 $skip 在 $limit 之后且 $skip >= $limit,则不会得到任何结果。简而言之,这是 MongoDB 中的预期行为。

  • 哇谢谢你!我一直在与“skip”、“limit”作斗争,并且想知道为什么 MongoDB 会表现得像这样(limit = limit + Skip)!通过切换订单,现在限制的表现符合我的预期。 (2认同)

lzl*_*31x 10

limit我发现和的顺序似乎skip并不重要。如果我指定skipbefore limit,mongoDB 将在后台进行limitbefore操作。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 */
}
Run Code Online (Sandbox Code Playgroud)

$skip如果我切换和会发生什么$limit?我在keysExamined和方面得到了相同的结果docsExamined

> 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 }",
}
Run Code Online (Sandbox Code Playgroud)

然后我检查了查询的解释结果。我发现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)
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

令人惊讶的是,我发现切换$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)
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,即使我指定了$skipbefore $limit,在explain结果中,它仍然是$limitbefore $skip