1. 程式人生 > >大資料量查詢優化

大資料量查詢優化

問題描述:涉及到大資料量,多迴圈查詢的時候,往往查詢的速度會變慢,影響系統的使用效能。該問題,在測試環境尚不明顯,因為測試環境的資料量畢竟是有限的。

但是,一旦將程式碼更新到線上的真實系統,因為資料量一下子增大,會造成資料查詢的緩慢,所造成的嚴重遲滯,就不能被忽略了。

業務場景:雲端計算系統。底層會將採集過來的宿主機流量資料,進行儲存。後臺管理系統,需要定時計算從底層傳遞過來的宿主機的頻寬的總體實際使用量,並且用實際使用量,除以節點的總頻寬,得到相關的出網、入網的頻寬使用率。


方案分析:
1.在儲存資料的時候,事實計算出資料使用量,後續查詢時,通過sql語句的sum方法直接彙總。
2.開啟多執行緒併發查詢。


一、利用Callable多執行緒,實現查詢優化的例子

原理同一個Callable的簡單例子。核心在於運用多執行緒,建立帶有返回值的查詢。

1.準備查詢引數

2.根據每頁資料量,去判斷應該建立的執行緒的數量。

3.根據資料的總數量,平均分配每個執行緒處理的數量。

4.將查詢返回的Future,儲存在list中(注意,這樣做可以提升查詢速度,先批量儲存結果,再處理的方式,要快於拿到值就處理的方式)

5.        latch.await();

6.遍歷List,資料處理+分頁,返回資料值。

public String services(){
		List <Future<Map<String,Object>>> list=new ArrayList<Future<Map<String,Object>>>();
		int total=0;
		//1.準備需要傳遞的引數
		String condition = dealContent();
		List<CompanyEntity> companyList = companyService.list(condition, -1, -1);
		List<Node> nodeList = new ArrayList<Node>();
		int nodeId = getNodeId();
			if(nodeId != 0){
				Node node = nodeService.getById(nodeId);
				nodeList.add(node);
			}else{
				nodeList = nodeService.getAll();
			}
		List<JSONObject> comList = new ArrayList<JSONObject>();
			for(CompanyEntity company : companyList){
				for(Node node : nodeList){
					JSONObject jsonObj = new JSONObject();
					jsonObj.put("companyName", company.getCompanyName());
					jsonObj.put("companyId", company.getCompanyId());
					jsonObj.put("companyType", company.getType());
					jsonObj.put("nodeName",node.getNode_name());
					jsonObj.put("nodeId",node.getNode_id());
					jsonObj.put("nodeIp", node.getNode_ip());
					comList.add(jsonObj);
				}
			}
		//2.資料分頁處理
		int pagesize=Integer.parseInt(super.getPagesize());
		int nopage=Integer.parseInt(super.getNowpage());
		int ThrearNum=0;
		if(pagesize>ThreadUtil.CORE_POOL_SIZE){
			ThrearNum=ThreadUtil.CORE_POOL_SIZE;
		}else{
			ThrearNum=pagesize;
		}
		try {
			//計算資料總量:
			int sumDataCount=comList.size();
			List<JSONObject> dataList=new ArrayList<JSONObject>();
			
			final CountDownLatch latch = new CountDownLatch(ThrearNum);
			boolean flag=false;
			int dataEnd=0;
			int addData=0;
			int j=1;
			for (int i = 0; i < ThrearNum; i++) {
				List<JSONObject> addList=new ArrayList<JSONObject>();
				List<JSONObject> tempList=new ArrayList<JSONObject>(comList.size());
				tempList=comList;
				List<JSONObject> dealList=new ArrayList<JSONObject>();
				
				if(sumDataCount%ThrearNum!=0){
					addData=sumDataCount%ThrearNum;
					flag=true;
				}
				//計算每個執行緒要處理的資料總量:
				int dataCountPerThread=sumDataCount/ThrearNum;
				//計算當前執行緒資料處理的起始索引:
				int dataStart=i*dataCountPerThread;
				//計算當前執行緒資料處理的結束索引:
				dataEnd=(i+1)*dataCountPerThread;
				//擷取List,作為當前執行緒所需要處理的全部資料:
				if(flag){
					if(j<=addData){
						//多餘的資料,從最後的索引開始平均分配
						addList.add(tempList.get(tempList.size()-j));
						j++;
					}
					dealList=subData(tempList,dataStart, dataEnd);
					if(addList.size()!=0){
						dealList.add(addList.get(0));
						addList.remove(0);
					}
				}else{
					dealList=subData(tempList,dataStart, dataEnd);
					//如果這裡不使用subData,而是使用subList,則會報錯:java.util.ConcurrentModificationException
					//dealList=comList.subList(dataStart, dataEnd);
				}
				
				Callable<Map<String,Object>> c1 = new CallableCountThread(condition,dealList,VpcFlag,slbFlag,blockFlag,vpnFlag,latch);
				Future<Map<String,Object>> f1=pool.submit(c1);
				list.add(f1);
			}
			
			latch.await();
			for(int i=0;i<list.size();i++){
				Future<Map<String,Object>> f1=list.get(i);
					total+=(Integer)f1.get().get("count");
					if(null!=f1.get().get("data")){
						dataList.addAll((List<JSONObject>)f1.get().get("data"));
					}
					
			}
			doPage_special(total);
			//3.查詢分頁開始和結束的索引位
			int  nowpage2 = (Integer) dataMap.get("nowpage"); 
			int  pagesize2 = (Integer) dataMap.get("pagesize");
			
			List<JSONObject> showData=new ArrayList<JSONObject>();
			if(dataList.size()==0){
				
			}else{
				if(nopage==1){
					if(dataList.size()<=pagesize2){
						showData=dataList.subList(0,dataList.size()-1);
						showData.add(dataList.get(dataList.size()-1));
					}else{
						showData=dataList.subList(0,pagesize2);
					}
				}else{
					if(dataList.size()<=(nopage)*pagesize2){
						showData=dataList.subList((nopage-1)*pagesize2,dataList.size()-1);
						showData.add(dataList.get(dataList.size()-1));
					}else{
						showData=dataList.subList((nopage-1)*pagesize2,nopage*pagesize2);
					}
				}
			}
			generateSuccessListResponse(showData);
		} catch (InterruptedException | ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return JSON;
	}

二、查詢效率

沒有使用duox多執行緒:

查詢時間在16s左右

使用多執行緒查詢:

查詢時間在1.7s左右

查詢速度提升了十倍。

三、心得總結:

1.寫程式碼的時候,不能僅僅只考慮功能的實現。特別是涉及到資料量比較大的時候,任何一次查詢的精簡,帶來的效率提升都是顯著的。

2.多執行緒在具體場景中的使用是非常重要的。在涉及到返回值的時候,就需要使用到Callable介面。

3.任何時候,寫完程式碼,都要自己給自己做code review。回過頭去看看自己寫的程式碼。能不能優化,能不能再優化。