高效分页查询结果

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" 正在排序的字段。