【发布时间】:2011-02-19 09:31:31
【问题描述】:
在使用 MongoDB 时,是否有任何特殊的制作模式,例如分页视图? 比如说一个列出 10 个最新帖子的博客,您可以在其中导航到旧帖子。
或者用一个索引来解决它,例如blogpost.publishdate 并跳过并限制结果?
【问题讨论】:
-
我将把这个挂起来,因为对于制作这个比例的正确方法似乎存在一些分歧。
在使用 MongoDB 时,是否有任何特殊的制作模式,例如分页视图? 比如说一个列出 10 个最新帖子的博客,您可以在其中导航到旧帖子。
或者用一个索引来解决它,例如blogpost.publishdate 并跳过并限制结果?
【问题讨论】:
当性能有问题或有大量集合时,使用 skip+limit 不是进行分页的好方法;随着页码的增加,它会变得越来越慢。使用 skip 需要服务器遍历从 0 到偏移(跳过)值的所有文档(或索引值)。
在传入最后一页的范围值时,最好使用范围查询(+ 限制)。例如,如果您按“发布日期”排序,您只需将最后一个“发布日期”值作为查询条件传递给获取下一页数据。
【讨论】:
可能的解决方案:尝试简化设计,考虑是否只能按 id 或某个唯一值排序?
如果我们可以,那么可以使用基于范围的分页。
常用的方法是使用 sort() 、 skip() 和 limit() 来实现分页。
【讨论】:
{ _id: { $gt: ... } }... 如果使用自定义订购,它根本不起作用 - 例如.sort(...).
当我的集合变得太大而无法在单个查询中返回时,这是我使用的解决方案。它利用 _id 字段的固有顺序,并允许您按指定的批量大小循环遍历集合。
这里是一个 npm 模块,mongoose-paging,完整代码如下:
function promiseWhile(condition, action) {
return new Promise(function(resolve, reject) {
process.nextTick(function loop() {
if(!condition()) {
resolve();
} else {
action().then(loop).catch(reject);
}
});
});
}
function findPaged(query, fields, options, iterator, cb) {
var Model = this,
step = options.step,
cursor = null,
length = null;
promiseWhile(function() {
return ( length===null || length > 0 );
}, function() {
return new Promise(function(resolve, reject) {
if(cursor) query['_id'] = { $gt: cursor };
Model.find(query, fields, options).sort({_id: 1}).limit(step).exec(function(err, items) {
if(err) {
reject(err);
} else {
length = items.length;
if(length > 0) {
cursor = items[length - 1]._id;
iterator(items, function(err) {
if(err) {
reject(err);
} else {
resolve();
}
});
} else {
resolve();
}
}
});
});
}).then(cb).catch(cb);
}
module.exports = function(schema) {
schema.statics.findPaged = findPaged;
};
像这样将它附加到您的模型上:
MySchema.plugin(findPaged);
然后像这样查询:
MyModel.findPaged(
// mongoose query object, leave blank for all
{source: 'email'},
// fields to return, leave blank for all
['subject', 'message'],
// number of results per page
{step: 100},
// iterator to call on each set of results
function(results, cb) {
console.log(results);
// this is called repeatedly while until there are no more results.
// results is an array of maximum length 100 containing the
// results of your query
// if all goes well
cb();
// if your async stuff has an error
cb(err);
},
// function to call when finished looping
function(err) {
throw err;
// this is called once there are no more results (err is null),
// or if there is an error (then err is set)
}
);
【讨论】:
基于范围的分页是可行的,但您需要聪明地了解如何最小/最大查询。
如果您负担得起,您应该尝试将查询结果缓存在临时文件或集合中。感谢 MongoDB 中的 TTL 集合,您可以将结果插入到两个集合中。
同时使用两者可确保当 TTL 接近当前时间时不会得到部分结果。您可以在存储结果时使用一个简单的计数器来执行非常简单的范围查询。
【讨论】:
这是一个使用官方 C# 驱动程序按CreatedDate(其中pageIndex 是从零开始)检索User 文档列表的示例。
public void List<User> GetUsers()
{
var connectionString = "<a connection string>";
var client = new MongoClient(connectionString);
var server = client.GetServer();
var database = server.GetDatabase("<a database name>");
var sortBy = SortBy<User>.Descending(u => u.CreatedDate);
var collection = database.GetCollection<User>("Users");
var cursor = collection.FindAll();
cursor.SetSortOrder(sortBy);
cursor.Skip = pageIndex * pageSize;
cursor.Limit = pageSize;
return cursor.ToList();
}
所有的排序和分页操作都是在服务器端完成的。虽然这是 C# 中的一个示例,但我想同样可以应用于其他语言端口。
【讨论】:
// file:ad-hoc.js
// an example of using the less binary as pager in the bash shell
//
// call on the shell by:
// mongo localhost:27017/mydb ad-hoc.js | less
//
// note ad-hoc.js must be in your current directory
// replace the 27017 wit the port of your mongodb instance
// replace the mydb with the name of the db you want to query
//
// create the connection obj
conn = new Mongo();
// set the db of the connection
// replace the mydb with the name of the db you want to query
db = conn.getDB("mydb");
// replace the products with the name of the collection
// populate my the products collection
// this is just for demo purposes - you will probably have your data already
for (var i=0;i<1000;i++ ) {
db.products.insert(
[
{ _id: i, item: "lamp", qty: 50, type: "desk" },
],
{ ordered: true }
)
}
// replace the products with the name of the collection
cursor = db.products.find();
// print the collection contents
while ( cursor.hasNext() ) {
printjson( cursor.next() );
}
// eof file: ad-hoc.js
【讨论】: