赞
踩
传统关系数据库中都提供了基于row number的分页功能,切换MongoDB后,想要实现分页,则需要修改一下思路。
假设一页大小为10条。则
//page 1
1-10
//page 2
11-20
//page 3
21-30
...
//page n
10*(n-1) +1 - 10*n
MongoDB提供了skip()和limit()方法。
skip: 跳过指定数量的数据. 可以用来跳过当前页之前的数据,即跳过pageSize*(n-1)。
limit: 指定从MongoDB中读取的记录条数,可以当做页面大小pageSize。
所以,分页可以这样做:
//Page 1
db.users.find().limit (10)
//Page 2
db.users.find().skip(10).limit(10)
//Page 3
db.users.find().skip(20).limit(10)
........
看起来,分页已经实现了,但是官方文档并不推荐,说会扫描全部文档,然后再返回结果。
The cursor.skip() method requires the server to scan from the beginning of the input results set before beginning to return results.
As the offset increases, cursor.skip() will become slower.
所以,需要一种更快的方式。其实和mysql数量大之后不推荐用limit m,n一样,
解决方案是先查出当前页的第一条,然后顺序数pageSize条。MongoDB官方也是这样推荐的。
我们假设基于_id的条件进行查询比较。事实上,这个比较的基准字段可以是任何你想要的有序的字段,比如时间戳。
//Page 1
db.users.find().limit(pageSize);
//Find the id of the last document in this page
last_id = ...
//Page 2
users = db.users.find({
'_id' :{ "$gt" :ObjectId("5b16c194666cd10add402c87")}
}).limit(10)
//Update the last id with the id of the last document in this page
last_id = ...
_id降序,第一页是最大的,下一页的id比上一页的最后的id还小。
function printStudents(startValue, nPerPage) {
let endValue = null;
db.students.find( { _id: { $lt: startValue } } )
.sort( { _id: -1 } )
.limit( nPerPage )
.forEach( student => {
print( student.name );
endValue = student._id;
} );
return endValue;
}
_id升序, 下一页的id比上一页的最后一条记录id还大。
function printStudents(startValue, nPerPage) {
let endValue = null;
db.students.find( { _id: { $gt: startValue } } )
.sort( { _id: 1 } )
.limit( nPerPage )
.forEach( student => {
print( student.name );
endValue = student._id;
} );
return endValue;
}
还有一共多少条和多少页的问题。所以,需要先查一共多少条count.
db.users.find().count();
先看ObjectId生成规则:
比如
"_id" : ObjectId("5b1886f8965c44c78540a4fc")
取id的前4个字节。由于id是16进制的string,4个字节就是32位,对应id前8个字符。即5b1886f8, 转换成10进制为1528334072. 加上1970,就是当前时间。
事实上,更简单的办法是查看org.mongodb:bson:3.4.3里的ObjectId对象。
public ObjectId(Date date) { this(dateToTimestampSeconds(date), MACHINE_IDENTIFIER, PROCESS_IDENTIFIER, NEXT_COUNTER.getAndIncrement(), false); } //org.bson.types.ObjectId#dateToTimestampSeconds private static int dateToTimestampSeconds(Date time) { return (int)(time.getTime() / 1000L); } //java.util.Date#getTime /** * Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT * represented by this <tt>Date</tt> object. * * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT * represented by this date. */ public long getTime() { return getTimeImpl(); }
MongoDB的ObjectId应该是随着时间而增加的,即后插入的id会比之前的大。但考量id的生成规则,最小时间排序区分是秒,同一秒内的排序无法保证。当然,如果是同一台机器的同一个进程生成的对象,是有序的。
如果是分布式机器,不同机器时钟同步和偏移的问题。所以,如果你有个字段可以保证是有序的,那么用这个字段来排序是最好的。_id则是最后的备选方案。
上面的分页看起来看理想,虽然确实是,但有个刚需不曾指明—我怎么跳页。
我们的分页数据要和排序键关联,所以必须有一个排序基准来截断记录。而跳页,我只知道第几页,条件不足,无法分页了。
现实业务需求确实提出了跳页的需求,虽然几乎不会有人用,人们更关心的是开头和结尾,而结尾可以通过逆排序的方案转成开头。所以,真正分页的需求应当是不存在的。如果你是为了查找某个记录,那么查询条件搜索是最快的方案。如果你不知道查询条件,通过肉眼去一一查看,那么下一页足矣。
说了这么多,就是想扭转传统分页的概念,在互联网发展的今天,大部分数据的体量都是庞大的,跳页的需求将消耗更多的内存和cpu,对应的就是查询慢。
当然,如果数量不大,如果不介意慢一点,那么skip也不是啥问题,关键要看业务场景。
我今天接到的需求就是要跳页,而且数量很小,那么skip吧,不费事,还快。
Google最常用了,看起来是有跳页选择的啊。再仔细看,只有10页,多的就必须下一页,并没有提供一共多少页,跳到任意页的选择。这不就是我们的find-condition-then-limit方案吗,只是他的一页数量比较多,前端或者后端把这一页给切成了10份。
前面关注于分页的实现原理,但忽略了排序。既然分页,肯定是按照某个顺序进行分页的,所以必须要有排序的。
MongoDB的sort和find组合
db.bios.find().sort( { name: 1 } ).limit( 5 )
db.bios.find().limit( 5 ).sort( { name: 1 } )
这两个都是等价的,顺序不影响执行顺序。即,都是先find查询符合条件的结果,然后在结果集中排序。
我们条件查询有时候也会按照某字段排序的,比如按照时间排序。查询一组时间序列的数据,我们想要按照时间先后顺序来显示内容,则必须先按照时间字段排序,然后再按照id升序。
db.users.find({name: "Ryan"}).sort( { birth: 1, _id: 1 } ).limit( 5 )
我们先按照birth升序,然后birth相同的record再按照_id升序,如此可以实现我们的分页功能了。
db.records.sort({ a:1, b:-1})
表示先按照a升序,再按照b降序。即,按照字段a升序,对于a相同的记录,再用b降序,而不是按a排完之后再全部按b排。
示例:
db.user.find(); 结果: { "_id" : ObjectId("5b1886ac965c44c78540a4fb"), "name" : "a", "age" : 1.0, "id" : "1" } { "_id" : ObjectId("5b1886f8965c44c78540a4fc"), "name" : "a", "age" : 2.0, "id" : "2" } { "_id" : ObjectId("5b1886fa965c44c78540a4fd"), "name" : "b", "age" : 1.0, "id" : "3" } { "_id" : ObjectId("5b1886fd965c44c78540a4fe"), "name" : "b", "age" : 2.0, "id" : "4" } { "_id" : ObjectId("5b1886ff965c44c78540a4ff"), "name" : "c", "age" : 10.0, "id" : "5" }
按照名称升序,然后按照age降序
db.user.find({}).sort({name: 1, age: -1}) 结果: { "_id" : ObjectId("5b1886f8965c44c78540a4fc"), "name" : "a", "age" : 2.0, "id" : "2" } { "_id" : ObjectId("5b1886ac965c44c78540a4fb"), "name" : "a", "age" : 1.0, "id" : "1" } { "_id" : ObjectId("5b1886fd965c44c78540a4fe"), "name" : "b", "age" : 2.0, "id" : "4" } { "_id" : ObjectId("5b1886fa965c44c78540a4fd"), "name" : "b", "age" : 1.0, "id" : "3" } { "_id" : ObjectId("5b1886ff965c44c78540a4ff"), "name" : "c", "age" : 10.0, "id" : "5" }
到这里必须考虑下性能。
$sort and Memory Restrictions
The 声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/56066Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。