聚合框架中的 $skip 和 $limit

Posted

技术标签:

【中文标题】聚合框架中的 $skip 和 $limit【英文标题】:$skip and $limit in aggregation framework 【发布时间】:2014-08-01 08:09:57 【问题描述】:

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

当 $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 规则。

【问题讨论】:

【参考方案1】:

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

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

顶部“排序”结果中内存保留的基本原理只能在它自己的“限制”内发挥作用,这对于超出一些合理“页面”数据的任何内容都不是最佳的。

超出内存消耗的合理范围,额外的阶段可能会产生负面影响而不是正面影响。

这些确实是当前形式的 MongoDB 可用的文本搜索功能的实际限制。但对于更详细且需要更高性能的任何内容,就像许多 SQL“全文”解决方案一样,您最好使用外部“专用”文本搜索解决方案。

【讨论】:

你说的现在的形式。是否正在进行增强 MongoDB 文本搜索的工作,你知道吗?这里有一些很棒的 cmets 将 Solr 与 MongoDB ***.com/questions/3215029/… 结合使用, @JohnBarça 您寻求的答案实际上更“官方”并且在性质上略微加载。诚然,IMO MongoDB 不会尝试成为“最佳”键/值存储,也不会尝试像“数据库”那样实现传统关系系统的所有功能。对此的扩展是通用“数据库”通常不会“进入”专业领域,例如“文本搜索”。但这是一种观点,观点往往会发生变化。无论如何,使用最有效的方法。 有趣。我一直在涉足 Mongo,并且非常喜欢某些功能。但我听到你在说什么。我是一名 GIS 人员,我喜欢已经完成的 geojson 内容和聚合空间增强功能,但就功能而言,离开 Postgres/Postgis 仍有很长的路要走。不过,我承认这是一个非常小众的领域。 @JohnBarça 我同意你们的看法。这只是一个临时解决方案,我可以用它快速简单地完成它。我们确实考虑过集成搜索引擎。但要等到下一阶段,因为它现在会带来太多额外的工作量。而且已经比我们现在用的“like”搜索好多了:) 我一直在思考这个问题,我想我明白为什么 MongoDB 只允许 $limit 而不是 $skip 来应用 top n 规则。因为skip+limit总是可以变成limit+skip。我编辑我的问题。【参考方案2】:

我发现limitskip 的顺序似乎无关紧要。如果我在limit 之前指定skip,mongoDB 将在skip 之前生成limit

> 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 ",

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

> 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)
        
    ]

如您所见,即使我在$limit 之前指定了$skip,在explain 结果中,它仍然是$limit$skip 之前。

【讨论】:

如何使$limit 是不可更改的。因为,我需要在一个循环中测试它,并且对于每个循环,当我用常数增加跳过时,每个请求(TTFB)的执行时间都会增加,因为 $limit 始终是 $skip + $limit。我们如何解决这个问题?

以上是关于聚合框架中的 $skip 和 $limit的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB——聚合管道之$limit&$skip&$sort操作

Mongodb聚合

聚合aggregate

mongoDB表与表的关系及聚合管道查询

MongoDB 聚合管道(Aggregation Pipeline)

Java自学-集合框架 聚合操作