java 深层次翻页的解决方案

41 篇文章 1 订阅

环境

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);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山鬼谣me

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值