获取集合总记录,不用countDocuments,改用 estimatedDocumentCount
分页用ObjectId排序定位(可结合其他查询条件),再用limit获取指定记录后的记录数。
limit获取的页大小,需要在匹配记录范围内,否则会导致 游标 hasNext 操作挂住。
比如:在5亿记录的集合里(索引有 day、ObjectId),指定 day=20220308的记录数有2000001,每次取5000条记录,需分401次查询。最后一次传参页大小应用1,而不是5000作为limit参数。如果大于1,会导致mongo又从指定day条件遍历符合条件数据。
注:以上场景是指用ObjectId作为索引的情况下,如果索引字段全部是数字类型,无此问题。
查询接口:
/**
* 根据查询条件查询数据(原生对象查询方式)
*
* @param collectionName 集合名称
* @param filter 过滤条件对象
* @param sorts 排序对象
* @param cls 返回对象类型
* @param page 查询页码(<=0表示不分页)
* @param pagesize 页大小(<=0表示获取匹配条件所有记录)
* @return
*/
public <T>Page<T> queryDocument(String collectionName, Bson filter, List<SortField> sorts, Class<T> cls, int page, int pagesize){
try {
MongoCollection<Document> collection = this.getCollectionObject(collectionName);
Page<T> ret = new Page<>();
ret.setPageInfo(page, pagesize, 0, 0, null);
long rows = 0;
int pages = 1;
FindIterable<Document> docs = null;
//查询
if (filter!=null) {
if (page>0) rows = collection.countDocuments(filter);
docs = collection.find(filter);
}
else {
if (page>0) rows = collection.estimatedDocumentCount();
docs = collection.find();
}
if (docs==null) return ret;
//处理排序
Bson sort = toQuerySort(sorts);
if (sort!=null) docs = docs.sort(sort);
//处理一页数据
if (pagesize>0) {
pages = calculatePage(rows, pagesize);
if (page>0) docs = docs.skip((page-1) * pagesize).limit(pagesize);
else docs = docs.limit(pagesize);
if (page>pages) return ret;
}
//取出数据
List<T> data = new ArrayList<>();
MongoCursor<Document> cursor = docs.iterator();
//注意: 在大数据情况下,如果页大小大于匹配的记录数,hasNext方法将挂起。正确做法应该传入匹配记录数作为页大小.
while (cursor.hasNext()) {
data.add(toBean(cursor.next(), cls));
}
cursor.close();
ret.setPageInfo(page, pagesize, pages, (int)rows, data);
return ret;
}catch(Exception e) {
Log4j2Tool.error(MongoCliDrv.class, e);
throw new AppException(SysErr.DB_SQL_ERR, "查询文档记录失败!");
}
}
具体应用场景:
/**
* 一次处理一天源数据
*
* @param log
* @return
*/
private static boolean splitAsinKeyword(LogAbaTask log) {
try {
// List<AbaKeywordsIndex> keysIdx = new ArrayList<>();
Map<String, AsinKeywords> swaps = new HashMap<>();
AbaMetricsDao dao = new AbaMetricsDao();
int total = dao.getDayRecordCount(log.getDay());
int count=0;
int remain=total;
List<AbaDataEx> list = null;
String lastId = log.getGuid();
while (remain>0) {
Log4j2Tool.info(AbaMetricsToAsinUtils.class, String.format("正在分析数据:%d/%d...", count,total));
list = dao.getAbaMetrics(log.getDay(), lastId, remain>BATCH_SIZE ? BATCH_SIZE : remain);
count += list.size();
remain = total-count;
for(AbaData item: list) {
String keyId = EncryptTool.md5Encode(item.getQuery());
// 转关键词列表
//keysIdx.add(new AbaKeywordsIndex(keyId, item.getQuery()));
// 转ASIN关键词
for(ClickAsin clickAsin: item.getClickAsins()) {
AsinKeywords asinKeywords = swaps.get(clickAsin.getAsin());
if (asinKeywords==null) asinKeywords = new AsinKeywords(clickAsin.getAsin(), item.getDay());
asinKeywords.addKeyword(keyId, item.getSearchRank());
swaps.put(clickAsin.getAsin(), asinKeywords);
}
}
// 保存关键词索引表(TODO 可以单独放一个任务执行)
//saveKeywordIndex(keysIdx);
// 获取下一批数据记录Id
lastId = list.get(list.size()-1).getGuid();
}
MongoCliDrv.closeConnection();
// 保存ASIN关键词表
saveAsinKeywords(log.getDay(), swaps);
log.setGuid(null);
Date currentDate = DateTimeTool.intToDate(log.getDay());
int nextDay = DateTimeTool.dateToInt(DateTimeTool.incDay(currentDate, 1));
log.setDay(nextDay);
log.setCollectionName(AbaMetricsDao.getCollectionName(nextDay));
Log4j2Tool.info(AbaMetricsToAsinUtils.class, log.getCollectionName()+" 已处理");
return true;
}catch(Exception e) {
Log4j2Tool.error(AbaMetricsToAsinUtils.class, e);
return false;
}
}