环境
jdk:1.7+
数据库:mongodb:3.4
前言
最近有个业务,查询的数据量很多,比如我要查询2007
-2018
年的数据,总共有98
万多条数据。
而我公司的交互,又是支持点击最后一页
,这样就会造成数据库skip
数据量很大,导致查询速度非常慢,大概是10秒
到13秒
的样子(已经建好了,最优的索引);
虽然网上都是建议,不要去查询最后一页的数据,只提供一定范围的数据,但是业务需求嘛!我们卖终端产品的,如果没有这个功能,反而显得不够强大。
思路
降序或者升序的最后一页,就是升序或者降序的第一页数据
基于这样的思路,查询的瓶颈就跑到中间去了。
也就是说,查询的性能,变成了一个抛物线:
这样好处是: 将瓶颈留在 点击次数最少的地方;就我公司业务而言;第一页和最后一页都是点击频率很高的地方。
代码
我的业务代码:
public static void mainInfoEvent(BasicDBObject infoQuery, int page, int rows,DBCollection information, Map<String, List<Integer>> eventMap,
List<Map<String, Object>> result, String order,int orderType, int totalCount) {
BasicDBObject fields = new BasicDBObject();
fields.append("id", 1);
fields.append("source_id", 1);
fields.append("source_table", 1);
fields.append("milestone", 1);
fields.append("stock_code", 1);
fields.append("stock_name", 1);
fields.append("event_name", 1);
fields.append("declare_date", 1);
fields.append("guid", 1);
int skip = (page-1)*rows;
int mod = totalCount % rows;
int totalPages = mod == 0 ? totalCount/rows : totalCount/rows +1;
int balancePoint = totalPages >>> 1;
if(totalCount > 100000){
if(page > balancePoint && totalPages > balancePoint){
//说明需要开始反向查询了
//首先要计算出最后一页的数据量来
//再算出 需要跳过的记录数
orderType *= -1;
int lastPageCount = totalCount - (totalPages-1)*rows;
if(page == totalPages){
skip = 0;
rows = lastPageCount;
}else{
int cpages = totalPages - page;
skip = (cpages-1) * rows + lastPageCount;
}
}
}
DBCursor infoCursor = information.find(infoQuery, fields).sort(new BasicDBObject(order, orderType)).skip(skip).limit(rows);
while(infoCursor.hasNext()){
DBObject o = infoCursor.next();
Integer id = o.getInteger("id");
Integer sourceId = o.getInteger("source_id");
String eventName = o.getString("event_name");
String tableName = getTableName(eventName);
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("source_id", sourceId);
map.put("source_table", tableName);
map.put("milestone", o.getString("milestone"));
map.put("stock_code", o.getString("stock_code"));
map.put("stock_name", o.getString("stock_name"));
map.put("event_name", eventName);
//提示时间
map.put("declare_date", o.getDate("declare_date"));
map.put("guid", o.getString("guid"));
List<Integer> eventList = eventMap.get(tableName);
if(eventList == null){
eventList = new ArrayList<Integer>();
eventList.add(sourceId);
eventMap.put(tableName, eventList);
}else{
eventList.add(sourceId);
}
//如果是事件总览就总表排序,否则就由各个分表排序
result.add(map);
}
infoCursor.close();
if(page > balancePoint && totalPages > balancePoint && totalPages != 0){
//将反向查询出来的数据,变成正常顺序
Collections.reverse(result);
}
}
①:要求前端把总记录数传过来;记住传总页数是没用的,因为总页数并不能算出总记录数,反之可以!
②:计算出最后一页的数据量
③:利用最后一页的数据量,算出需要跳过的记录数
int lastPageCount = totalCount - (totalPages-1)*rows;
if(page == totalPages){
skip = 0;
rows = lastPageCount;
}else{
int cpages = totalPages - page;
skip = (cpages-1) * rows + lastPageCount;
}
最后一页的数据,特殊处理下;
其他的数据就可以套用(cpages-1) * rows + lastPageCount;
就行了!
在最后要记得,进行倒序Collections.reverse(result);
,否则查询出来的顺序虽然确实是最后一页的,但是顺序不对!
中间页数的查询时间,大概是 5秒左右,对于深层次翻页查询,勉勉强强!
------------------------------------------2018/09/18----------------------------------------------------
今天将主要代码抽成公共代码:
/**
* 超过10万的数据进行反向查询
* @param page
* @param rows
* @param fields 显示的字段
* @param information 集合对象
* @param order 排序字段
* @param orderType 排序类型
* @param totalCount 总记录数
* @param inputSkip 导出的起始位置
* @param infoQuery 查询条件
* @return
* @author yutao
* @date 2018年9月18日下午2:50:44
*/
public static DBCursor tenWanOrderCollection(int page, int rows, BasicDBObject fields, DBCollection information, String order,
int orderType, int totalCount, Integer inputSkip,
BasicDBObject infoQuery){
int skip = (page-1)*rows;
if (inputSkip != null) {//跳页导出传入的参数
skip = inputSkip;
}
int mod = totalCount % rows;
int totalPages = mod == 0 ? totalCount/rows : totalCount/rows +1;
int balancePoint = totalPages >>> 1;
if(totalCount > 100000){
if(page > balancePoint && totalPages > balancePoint){
//说明需要开始反向查询了
//首先要计算出最后一页的数据量来
//再算出 需要跳过的记录数
orderType *= -1;
if (inputSkip != null && inputSkip <= totalCount) {//跳页导出传入的参数
if (totalCount - inputSkip < rows) {
skip = 0;
rows = totalCount - inputSkip;
} else {
skip = totalCount - (inputSkip + rows);
}
} else {
int lastPageCount = totalCount - (totalPages-1)*rows;
if(page == totalPages){
skip = 0;
rows = lastPageCount;
}else{
int cpages = totalPages - page;
skip = (cpages-1) * rows + lastPageCount;
}
}
}
}
return information.find(infoQuery, fields).sort(new BasicDBObject(order, orderType)).skip(skip).limit(rows);
}
具体调用:
DBCursor cursor = tenWanOrderCollection(page, rows, fields, collection, order, orderType, totalCount, skip, query);
//...
反转代码:
/**
* 反转结果 公用代码
* @param page
* @param rows
* @param totalCount 总记录数
* @param result 结果
* @author yutao
* @return
* @date 2018年9月18日下午2:59:28
*/
private static void reverseResult(int page, int rows, int totalCount,
LinkedHashMap<String, Map<String, Object>> result) {
int mod = totalCount % rows;
int totalPages = mod == 0 ? totalCount/rows : totalCount/rows +1;
int balancePoint = totalPages >>> 1;
LinkedHashMap<String, Map<String, Object>> linkResult = new LinkedHashMap<>();
if(totalCount > 100000){
if(page > balancePoint && totalPages > balancePoint && totalPages != 0){
//将反向查询出来的数据,变成正常顺序
ListIterator<Entry<String, Map<String, Object>>> iterator = new ArrayList<Map.Entry<String, Map<String,Object>>>( result.entrySet()).listIterator(result.size());
while(iterator.hasPrevious()){
Entry<String, Map<String, Object>> previous = iterator.previous();
String key = previous.getKey();
Map<String, Object> value = previous.getValue();
linkResult.put(key, value);
}
result.clear();
result.putAll(linkResult);
}
}
}
具体调用:
reverseResult(page, rows, totalCount, result);