问题
在Java中使用mongodb的MongoTemplate进行分页时,一般的策略是使用skip+limit的方式,但是这种方式在需要略过大量数据的时候就显得很低效(即skip趋向于变大)。
而且, 在数据查询语句中使用到了SORT排序的时候,mongo很容易内存溢出,报出如下错误:
lan executor error during find: Overflow sort stage buffered data usage of 33568573 bytes exceeds internal limit of 33554432 bytes
sort会将当前搜索出来的数据放入缓存,进行排序,如果一次性查询的输出量太大,超过33554432bytes,即报内存溢出异常
这种情况下要么去掉sort,要么减少一次性查询的输出量。
但是,sort是业务需求,能做的优化就是从一次性查询的输出量上想办法。
方法
目前我实践的有两种分页方法:
一、将上一页最后一条数据的时间戳作为搜索条件。这样做有一个局限,只能在连续跳页中使用,如果是间断式跳页,就无法满足。
二、如果skip略过量超过5000条,将搜索结果按5000条进行第一层分页,每次获取5000条数据中的最后一条数据的时间戳,用作下一次的查询条件,每次循环skip略过量就减少5000,循环直至skip少于5000条,然后再skip,代码如下:
int pageSkip = 5000;
if(s.getOffset()>pageSkip){
int offset = s.getOffset();
int cycleCount = s.getOffset()/pageSkip;
//每次查询5000条数据
long lastUpdateTime = 0;
while(cycleCount>0){
//分段查询
Query queryNew = getDBComponent().getQueryBuilder(s.getClass()).buildQuery(s);
queryNew.addCriteria(Criteria.where("dr").is(BaseObject.DR_YES));//dr 软删除下可见的数据
queryNew.with(new Sort(Sort.Direction.DESC,"lastUpdateTime"));
queryNew.skip(pageSkip);
if(lastUpdateTime!=0){
queryNew.addCriteria(Criteria.where("lastUpdateTime").lt(lastUpdateTime));
}
BaseObject baseObject = (BaseObject) getDBComponent().getMongoDao().findOne(queryNew);
lastUpdateTime = baseObject.getLastUpdateTime();
cycleCount--;
offset-=pageSkip;
}
query.skip(offset);
query.addCriteria(Criteria.where("lastUpdateTime").lt(lastUpdateTime));
}else{
query.skip(s.getOffset());
}