聚合框架中的 $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】:我发现limit
和skip
的顺序似乎无关紧要。如果我在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
会发生什么?我在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 ",
然后我检查了查询的解释结果。我发现totalDocsExamined
在limit
阶段已经是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操作