最近在公司中使用MongoDB作为数据库处理一些数据。而处理数据时,不可避免的就要使用到分页。
如果你对MongoDB有些了解的话,大概会想到利用skip() 和limit()组合使用来达到分页的效果。 例如:
db.collection.find().skip(10).lim
这两个函数在使用的过程中也确实像MySQL中的 "limit x,y" 的效果类型。但实际上,在数据量过大的情况下,如果skip的数量较大,就会导致查询性能很慢,在MongoDB的官方文档中,我们也可以看到对skip在分页中应用的说明。
所以,在分页中使用skip(),尤其是数据量较大的情况下,是非常不明智的。官方推荐的做法是使用某个字段排序,并使用该字段作为参数使用gt 来达到分页的效果。代码如下
db.students.find( { _id: { $lt: startValue } } ) .sort( { _id: -1 } ) .limit( nPerPage ) .forEach( student => { print( student.name ); endValue = student._id; } );
但是这种方法无法实现跳页。例如从第一页跳到第三页。因为无法确定用于比较的字段值。
我的解决方案是使用了MongoDB自动生成的ObjectId作为排序字段,但是不利用gt,而仍然使用skip。在访问第一页数据的时候,使用逆序,尾页数据的使用,使用顺序排序。这样可以保证大部分情况下 skip()都在一个很小的数值上。既实现了分页也能够保证跳页的需求。不过性能上要差一点。是一个折中的方案,下面是代码实现。
int count = (int) mongoCollection.count(filter); //获取count List<Bson> sortList = new ArrayList<>(); int skip = 0; if (offset < count/2) { //skip的值靠近首 if (sort == null) { //增加_id排序 sortList.add(Sorts.descending("_id")); } else { sortList.add(Sorts.descending("_id")); sortList.add(sort); } } else {//skip的值靠近尾,需要特殊处理skip和limit的值 skip = count - offset; if (skip < 0){ return result; } if (skip - size < 0) { size = skip; skip = 0; } else { skip = skip - size; } if (sort == null) { sortList.add(Sorts.ascending("_id")); } else { sortList.add(Sorts.ascending("_id")); sortList.add(sort); } } Bson finalSort = Filters.and(sortList); FindIterable<Document> iterableResult = mongoCollection.find(filter).sort(finalSort).skip(skip).limit(size);