高效分页查询结果
Efficiently page query results
我已 mongo 连接到 Web 应用程序(Python/Pyramid 框架)并且我想在浏览器中显示大量集合。由于它非常大,我一次只想呈现 100 条记录,允许用户单击 "load more" 按钮来检索接下来的 100 条记录。本质上是分页。
但是,必须按整数分值降序检索文档,分值可以是 0-100。超过 100 个文档,有多个文档具有相同的分数。因此,分页比仅获取分数小于或等于检索到的最后一个文档的下 100 个文档要复杂一些。
如果我可以只保存光标,那就太棒了,这样当请求下一个 100 个时,光标可以从它停止的地方开始。我试图避免只做 query.skip(x).limit(100)
因为我读过这不是很有效,因为它基本上检索了你跳过的所有文档。
但如果我根据非索引字段的排序检索文档,我不知道 skip/limit 方法的效率会降低多少(如果有的话)。
我知道这里还有很多其他选择,可以随意提及,但我也真的很好奇这样的事情是否可能。
PS我已经试过了pickle.dumps
……不行。
请原谅这里的 JavaScript,但它确实可以作为一个示例,可以在 shell 中为每个人复制,我正在快速完成。任何语言的要点基本相同
考虑以下文档:
{ "_id": 1, "score": 2 }
{ "_id": 2, "score": 2 }
{ "_id": 3, "score": 5 }
{ "_id": 4, "score": 4 }
{ "_id": 5, "score": 5 }
{ "_id": 6, "score": 3 }
{ "_id": 7, "score": 1 }
{ "_id": 8, "score": 2 }
现在对于此处的示例,结果将按 "score" 降序排序,并且一次限制为 "pages" 个 2 个结果。然后将像这样发出初始查询:
var seenIds = [];
var lastScore = 0;
var cursor = db.sorted.find({}).sort({ "score": -1 }).limit(2);
cursor.forEach(function(doc) {
printjson(doc);
if (doc.score != lastScore)
seenIds = [];
seenIds.push(doc._id)
lastScore = doc.score;
});
输出将来自排序后的结果:
{ "_id" : 3, "score" : 5 }
{ "_id" : 5, "score" : 5 }
因此,基本思想是您通过游标进行迭代,执行您必须执行的任何处理,例如输出流或构建另一个变量的内容。当您迭代时,您想要存储在最后一页结果中看到的 "unique" _id
值的数组。您还保留存在的已排序字段的值。为了实现最佳效果,您只需要在其中保留尽可能多的 "unique" _id
值作为最后一个 "seen".
分数的当前值。
当然,这些变量需要在请求之间存储,例如在会话存储中。所以这样做,以便您可以在下一个请求中获取它们。
在任何后续的 "load more" 请求中,您将发出如下更改后的查询:
var cursor = db.sorted.find({
"_id": { "$nin": seenIds }, "score": { "$lte": lastScore }
}).sort({ "score": -1 }).limit(2);
cursor.forEach(function(doc) {
printjson(doc);
if (doc.score != lastScore)
seenIds = [];
seenIds.push(doc._id)
lastScore = doc.score;
});
请注意,存储中这些变量的输入状态将如下所示:
seenIds = [ 3, 5 ];
lastScore = 5;
所以查询的输出是:
{ "_id" : 4, "score" : 4 }
{ "_id" : 6, "score" : 3 }
现在会话存储中的那些新状态变量将包含不同的值:
seenIds = [ 6 ];
lastScore = 3;
并且获取 "load more" 请求使用这些获取:
{ "_id" : 1, "score" : 2 }
{ "_id" : 2, "score" : 2 }
因此,如您所见,其核心是按 "score" 之类的排序时,一般的想法是仅检索 "less than or equal to"(降序)到 "lastSeen" 在页面中检索到的值。
当然有可能许多项目可能具有相同的 "score" 值,因此为了解决这个问题,您保留了一个已经看到相同值的 "unique id's" 列表匹配分数值。
查询选择基本上是说,“获取分数小于或等于我上次看到的值的所有结果,但排除我已经从结果中看到的任何内容。
这样做可以有效地 "forward only paging" 通过结果而无需 .skip()
和 .limit()
开销。为获得最佳性能,请确保您确实 "index" 正在排序的字段。
我已 mongo 连接到 Web 应用程序(Python/Pyramid 框架)并且我想在浏览器中显示大量集合。由于它非常大,我一次只想呈现 100 条记录,允许用户单击 "load more" 按钮来检索接下来的 100 条记录。本质上是分页。
但是,必须按整数分值降序检索文档,分值可以是 0-100。超过 100 个文档,有多个文档具有相同的分数。因此,分页比仅获取分数小于或等于检索到的最后一个文档的下 100 个文档要复杂一些。
如果我可以只保存光标,那就太棒了,这样当请求下一个 100 个时,光标可以从它停止的地方开始。我试图避免只做 query.skip(x).limit(100)
因为我读过这不是很有效,因为它基本上检索了你跳过的所有文档。
但如果我根据非索引字段的排序检索文档,我不知道 skip/limit 方法的效率会降低多少(如果有的话)。
我知道这里还有很多其他选择,可以随意提及,但我也真的很好奇这样的事情是否可能。
PS我已经试过了pickle.dumps
……不行。
请原谅这里的 JavaScript,但它确实可以作为一个示例,可以在 shell 中为每个人复制,我正在快速完成。任何语言的要点基本相同
考虑以下文档:
{ "_id": 1, "score": 2 }
{ "_id": 2, "score": 2 }
{ "_id": 3, "score": 5 }
{ "_id": 4, "score": 4 }
{ "_id": 5, "score": 5 }
{ "_id": 6, "score": 3 }
{ "_id": 7, "score": 1 }
{ "_id": 8, "score": 2 }
现在对于此处的示例,结果将按 "score" 降序排序,并且一次限制为 "pages" 个 2 个结果。然后将像这样发出初始查询:
var seenIds = [];
var lastScore = 0;
var cursor = db.sorted.find({}).sort({ "score": -1 }).limit(2);
cursor.forEach(function(doc) {
printjson(doc);
if (doc.score != lastScore)
seenIds = [];
seenIds.push(doc._id)
lastScore = doc.score;
});
输出将来自排序后的结果:
{ "_id" : 3, "score" : 5 }
{ "_id" : 5, "score" : 5 }
因此,基本思想是您通过游标进行迭代,执行您必须执行的任何处理,例如输出流或构建另一个变量的内容。当您迭代时,您想要存储在最后一页结果中看到的 "unique" _id
值的数组。您还保留存在的已排序字段的值。为了实现最佳效果,您只需要在其中保留尽可能多的 "unique" _id
值作为最后一个 "seen".
当然,这些变量需要在请求之间存储,例如在会话存储中。所以这样做,以便您可以在下一个请求中获取它们。
在任何后续的 "load more" 请求中,您将发出如下更改后的查询:
var cursor = db.sorted.find({
"_id": { "$nin": seenIds }, "score": { "$lte": lastScore }
}).sort({ "score": -1 }).limit(2);
cursor.forEach(function(doc) {
printjson(doc);
if (doc.score != lastScore)
seenIds = [];
seenIds.push(doc._id)
lastScore = doc.score;
});
请注意,存储中这些变量的输入状态将如下所示:
seenIds = [ 3, 5 ];
lastScore = 5;
所以查询的输出是:
{ "_id" : 4, "score" : 4 }
{ "_id" : 6, "score" : 3 }
现在会话存储中的那些新状态变量将包含不同的值:
seenIds = [ 6 ];
lastScore = 3;
并且获取 "load more" 请求使用这些获取:
{ "_id" : 1, "score" : 2 }
{ "_id" : 2, "score" : 2 }
因此,如您所见,其核心是按 "score" 之类的排序时,一般的想法是仅检索 "less than or equal to"(降序)到 "lastSeen" 在页面中检索到的值。
当然有可能许多项目可能具有相同的 "score" 值,因此为了解决这个问题,您保留了一个已经看到相同值的 "unique id's" 列表匹配分数值。
查询选择基本上是说,“获取分数小于或等于我上次看到的值的所有结果,但排除我已经从结果中看到的任何内容。
这样做可以有效地 "forward only paging" 通过结果而无需 .skip()
和 .limit()
开销。为获得最佳性能,请确保您确实 "index" 正在排序的字段。