最近在公司中使用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);