MongoDB – 分页

在使用MongoDB的时候,有没有特殊的模式来制作页面视图? 说一个博客,列出10个最新的职位,你可以向后导航到旧的职位。

或者用一个索引来解决它,例如blogpost.publishdate,只是跳过并限制结果?

使用skip + limit不是在性能出现问题时进行分页的好方法; 当您增加页码时,它会变得越来越慢。 使用跳过要求服务器遍历所有文档(或索引值)从0到偏移量(跳过)的值。

在最后一页的范围值中传递范围查询(+限制)会更好。 例如,如果按“发布date”进行sorting,则可以简单地传递最后一个“发布date”值作为查询条件以获取下一页数据。

  1. 如果您需要以多种方式对项目进行sorting,基于范围的分页很难实现。
  2. 请记住,如果sort参数的字段值不唯一,那么基于范围的分页将变得不可实现。

所以有一种方法是试图简化devise,思考如果我们只能通过id或一些独特的价值进行sorting?

如果可以的话,则可以使用基于范围的寻呼。

常用的方法是使用sort(),skip()和limit()来实现上述的分页。

这是当我的collections变得太大而无法在单个查询中返回时所使用的解决scheme。 它利用_id字段的固有顺序,并允许按指定的批量大小循环访问集合。

这里是一个npm模块, mongoose分页 ,完整代码如下:

 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) } ); 

基于范围的分页是可行的,但是你需要聪明的如何你最小/最大的查询。

如果你能负担得起,你应该尝试在一个临时文件或集合中caching查询的结果。 感谢MongoDB中的TTL集合,您可以将结果插入到两个集合中。

  1. search+用户+参数查询(TTL无论)
  2. 查询结果(TTL无论+清理间隔+ 1)

使用两者都能确保当TTL接近当前时间时不会得到部分结果。 当你存储结果来做一个非常简单的范围查询的时候,你可以使用一个简单的计数器。

下面是使用官方C#驱动程序检索CreatedDate (其中pageIndex是从零开始)的User文档sorting列表的示例。

 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(); } 

所有的sorting和分页操作都在服务器端完成。 尽pipe这是C#中的一个例子,但我猜也可以应用到其他语言的端口。

请参阅http://docs.mongodb.org/ecosystem/tutorial/use-csharp-driver/#modifying-a-cursor-before-enumerating-it

  // 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