`
25707332
  • 浏览: 27234 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

记一次代码优化—大数组拆分查询

阅读更多


    需求:

     同一页面显示的内容需要到2个不同的数据库a,b中查询,a的结果中的id作为条件再到b中查询。
     如a的结果数据量大,需要分次到b中查询。

 

    JDK:1.6

    持久层框架:ibatis2.0

 

    要注意的细节:

    1.循环次数

    2.内存占用

 

public PageResult queryForPageListNew(int pageSize, int pageNum,AForm queryForm)
	{
		// 初始化循环次数
		int i = 0;
		// 初始化n
		int n = 200;
		// 获得a表单
		PageResult pr = aDAO.queryForPageResult(queryForm
				.getParams(), pageSize, pageNum, A.class);
		List<A> aList = pr.getResultList();
		// 初始化size
		int size = aList.size();
		// 创建临时数组,用于存放a表单中的id
		List<Long> temp = new ArrayList<Long>();
		// 初始化hashmap,size*2避免hashmap resize()
		Map<Long, Long> countMap = new HashMap<Long, Long>(size*2);
		for (A a : aList)
		{
			i++;
			temp.add(a.getId());
			// 每n条数据查询一次数据库
			if (((i + n) % n == 0 && i < size)||(i==size))
			{
				// 由于使用某种特殊框架自动生成DAOImpl,目前只能返回List
				List<Map<String, Long>> countMapList = bDAO
						.queryCountByOwnerForList(temp);
				// 将查询结果放入countMap中
				for (Map<String, Long> map : countMapList)
				{
					Long id = map.get("owner");
					Long count = map.get("count");
					countMap.put(id, count);
				}
				temp.clear();
			}
		}
		temp = null;
		for (A a : aList)
		{
			a.setCount(countMap.get(a.getId()));
		}
		countMap.clear();
		countMap = null;
		return pr;
	}

    b的ibatis SqlMap:

<select id="queryCountByOwnerForList" resultMap="resultMap.B" parameterClass="List">
        select 
        	t1.owner,
        	count(t1.owner) as count        	
		from t_b t1 
		where
			(
			 t1.status= 4 
			 or t1.status=5 
			 or t1.status=6
			)
			and t1.owner in 
        <iterate open= "(" close = ")" conjunction = ",">
			#[]#
        </iterate>
        group by t1.owner
</select>

    HashMap countMap 使用说明:

    两次的查询结果,需要比对id,进行组合。2次查询的结果集均未List,如直接组合,需要做for循环的嵌套,比对方式如下:

		for(A a: aList){ 
			for(B b: countMapList){
				if(a.id==b.id)
				{
					组合...
				}
			}
		}

    这样就要循环n的平方次,才能组合查询结果,使用HashMap只需要循环2n次

 

    这里假定使用HashMap存取结果比List嵌套循环快(没经过测试)

 

 

    改进思考:

     1.hashmap初始化的值应为

     大于 (int)(size/0.75)+1 且最小的2的n次方

     参见:http://www.iteye.com/topic/539465    

     2.不知道ibatis是否支持参数为List<Object>,并获得Object的属性。如果可以,能否省掉temp?

 

    代码初评:

    1.int n = 1000;

    常量应该设置为fanal static

 

    2.int size = aList.size();

    可以直接用aList.size(),没有必要再定义一个变量

 

    3.List<Long> temp = new ArrayList<Long>();

    长度也可以初始化

 

    4.if (((i + n) % n == 0 && i < size)||(i==size))

    太复杂,不容易看懂,尽量用面对对象的思想,而且i<size的条件也是多余的

 

    5.没有考虑第一次查询aList为空的情况,程序仍在继续往下执行

 

    初评过后的代码:

 

private final static int SELECT_IN_NUMBER=200;

public PageResult queryForPageListNew(int pageSize, int pageNum,
			CorpClaimSetQueryForm queryForm)
	{
		// 获得a查询分页表单
		PageResult pr = aDAO.queryForPageResult(queryForm
				.getParams(), pageSize, pageNum, A.class);
		List<A> aList = pr.getResultList();
		
		// 如果cssList查询结果为空直接返回pr
		if (aList.size() <= 0)
		{
			return pr;
		}

		// 创建临时数组,用于存放a表单中的id
		List<Long> temp = new ArrayList<Long>(SELECT_IN_NUMBER);

		// 初始化hashmap,size*2避免hashmap resize()
		Map<Long, Long> countMap = new HashMap<Long, Long>(aList.size() * 2);
		for (A a : aList)
		{
			temp.add(a.getId());

			// 每n条数据查询一次数据库
			if (temp.size() == SELECT_IN_NUMBER)
			{
				List<Map<String, Long>> countMapList = bDAO
						.queryCountByOwnerForList(temp);

				// 将查询结果放入countMap中
				for (Map<String, Long> map : countMapList)
				{
					Long id = map.get("owner");
					Long count = map.get("count");
					countMap.put(id, count);
				}
				//每查询1次,清空一次temp
				temp.clear();
			}
		}
		
		//对temp中余留的数据进行查询
		if (!temp.isEmpty())
		{
			List<Map<String, Long>> countMapList = bDAO
					.queryCountByOwnerForList(temp);
			for (Map<String, Long> map : countMapList)
			{
				Long id = map.get("owner");
				Long count = map.get("count");
				countMap.put(id, count);
			}
			temp.clear();
			temp = null;
		}
        
		//将统计结果加入到原有的查询结果中
		for (A a : aList)
		{
			a.setCount(countMap.get(a.getId()));
		}
		countMap.clear();
		countMap = null;
		return pr;
	}
 

 

     再次评审:

     1.集合调用clear()之后,不需要再设置为null(方法调用完后,内存会自动释放,这些操作都没必要?)

 

     2.一段代码中用到相同的代码2次,这段相同的代码应该提炼出来,自成一个方法

 

 

     最终代码:

 

private final static int SELECT_IN_NUMBER = 200;

/**
	 * 分页查询 在原有的基础上增加数量统计,并可进行批量统计,每n条统计一次。
	 * @param pageSize
	 * @param pageNum
	 * @param queryForm
	 * @return PageResult
	 */
	@SuppressWarnings("unchecked")
	@Override
	public PageResult queryForPageListNew(int pageSize, int pageNum,
			CorpClaimSetQueryForm queryForm)
	{
		// 获得a查询分页表单
		PageResult pr = aDAO.queryForPageResult(queryForm
				.getParams(), pageSize, pageNum,A.class);
		List<A> aList = pr.getResultList();
		
		// 如果aList查询结果为空直接返回pr
		if (aList.size() <= 0)
		{
			return pr;
		}

		// 创建临时数组,用于存放a中的id
		List<Long> temp = new ArrayList<Long>(SELECT_IN_NUMBER);

		// 初始化hashmap,size*2避免hashmap resize()
		Map<Long, Long> countMap = new HashMap<Long, Long>(aList.size() * 2);
		for (A a : aList)
		{
			temp.add(a.getId());

			// 每n条数据查询一次数据库
			if (temp.size() == SELECT_IN_NUMBER)
			{
				queryAndWarpCountMap(temp, countMap);
			}
		}
		
		//对temp中余留的数据进行查询
		if (!temp.isEmpty())
		{
			queryAndWarpCountMap(temp, countMap);
		}
        
		//将统计结果加入到原有的查询结果中
		for (A a : aList)
		{
			a.setCount(countMap.get(a.getId()));
		}
		return pr;
	}

	/**
	 * 根据临时ID集合,查询统计数据,并且包装到统计map中。
	 * 最后清空临时ID集合
	 * @param temp
	 * @param countMap
	 */
	private void queryAndWarpCountMap(List<Long> temp, Map<Long, Long> countMap) {
		List<Map<String, Long>> countMapList = bDAO
				.queryCountByOwnerForList(temp);
		for (Map<String, Long> map : countMapList)
		{
			Long id = map.get("owner");
			Long count = map.get("count");
			countMap.put(id, count);
		}
		//每查询1次,清空一次temp
		temp.clear();
	}

 

分享到:
评论
20 楼 mawt 2010-08-06  
能不能在一个数据库上建张表,用来放要查询的另一个数据库中的数据.这个数据每次查询时先放进去,查询完成后再清空
操作如下:
数据库a,数据库b
1.在b中建个表用来放a中的结果
2.把数据库a中要用的数据统计(可能不用统计)后写入到b数据库中,
  同时写入的数据用个自动增长的id(大数据时可以分页)
3.这时a中想要的数据就到了b中,这时一条sql就应该可以解决了,这时的效率应该会高点吧.
19 楼 jychenok 2010-08-05  
蔡华江 写道
25707332 写道
mathfox 写道
太复杂了,没有看

不过Oracle有dblink这个东西,能支持两个数据库一起查。


!
那我就不知道为什么项目经理叫我这样做了。。。


我也想说有dblink,只是不知道dblink效率怎么样,以前用来导些数据,感觉不是很好


dblink如果涉及到大数据量还是会有一定性能影响的,特别是涉及到两个不同服务器时....
我们的数据库是使用相同的服务器,所以基本还是能够接受,比写代码确实简化了很多很多...
还有就是oracle中dblink与blob字段合用会不稳定的出现问题,如果再加上临时表...就很容易出现问题..
18 楼 25707332 2010-08-05  
gongmingwind 写道
dblink还是物化视图要以需求而定,优化过程不错


饿 谢谢 本来想和大家一起讨论下代码优化或重构,结果大家都讨论dblink去了  囧~
17 楼 gongmingwind 2010-08-05  
dblink还是物化视图要以需求而定,优化过程不错
16 楼 victorlin23 2010-08-05  
mathfox 写道
蔡华江 写道
25707332 写道
mathfox 写道
太复杂了,没有看

不过Oracle有dblink这个东西,能支持两个数据库一起查。


!
那我就不知道为什么项目经理叫我这样做了。。。


我也想说有dblink,只是不知道dblink效率怎么样,以前用来导些数据,感觉不是很好


没有测试过,不过感觉用的正确的话,怎么也比JAVA代码快的多吧。

我现在天天用dblink导数据仓库的数据。


不使用dblink,还可以选择使用物化视图,物化视图是远程数据的的本地副本,或者用来生成基于数据表求和的汇总表。物化视图存储基于远程表的数据,也可以称为快照。而且物化视图可以配置成当远程数据有修改时实时更新物化视图。保证数据的一致性。建议考虑
15 楼 whiletrue 2010-08-04  
dblink比较慢,导过去再查最快,哈哈.不过要看场景了.
14 楼 25707332 2010-08-04  
饿。。我想说  重点是优化过程  不是功能。。。
13 楼 wujiazhao88 2010-08-04  
写个中间件,实现异构数据的join,以后可以重用了。
12 楼 mathfox 2010-08-04  
蔡华江 写道
25707332 写道
mathfox 写道
太复杂了,没有看

不过Oracle有dblink这个东西,能支持两个数据库一起查。


!
那我就不知道为什么项目经理叫我这样做了。。。


我也想说有dblink,只是不知道dblink效率怎么样,以前用来导些数据,感觉不是很好


没有测试过,不过感觉用的正确的话,怎么也比JAVA代码快的多吧。

我现在天天用dblink导数据仓库的数据。
11 楼 25707332 2010-08-04  
hanxing0331 写道
bDAO.queryCountByOwnerForList(temp) 这个里面是怎么查的,如果也是按每个id查一次,那每n条id查一次数据库和每个id查一次数据的差距有多少呢?那 temp 这个是不是也多余呢? 这点有点不明白


后面有我的sql  每次统计一批数据比1次统计1条快
10 楼 抛出异常的爱 2010-08-04  
class Page implements Iterator<List>{
	int page = 5;
	int start = 0 ;
	int split = start + page ;
	List list ;
	public Page(List list ){
		this.list = list;
	}
	public Page(List list ,int page ){
		this.list = list;
		this.page =page;
	}
	public boolean hasNext() {		
		return start < list.size();
	}
	public List next() {
		List result = list.subList(start, split );
		start  = split;
		split += page;
		split  = Math.min(split,list.size());		
		return result;
	}
	public void remove()  {
		throw new RuntimeException("not to change");
	}
	
}

拆分数据 .
用法:
		List list = new ArrayList<String>();
		for(int i = 0 ; i <10; i++){
			list.add("A"+i);
		}

		Iterator<List> p = new Page(list);
	
		while(p.hasNext()){
			
			System.out.println(p.next());
			
		}
9 楼 hanxing0331 2010-08-04  
bDAO.queryCountByOwnerForList(temp) 这个里面是怎么查的,如果也是按每个id查一次,那每n条id查一次数据库和每个id查一次数据的差距有多少呢?那 temp 这个是不是也多余呢? 这点有点不明白
8 楼 蔡华江 2010-08-04  
25707332 写道
mathfox 写道
太复杂了,没有看

不过Oracle有dblink这个东西,能支持两个数据库一起查。


!
那我就不知道为什么项目经理叫我这样做了。。。


我也想说有dblink,只是不知道dblink效率怎么样,以前用来导些数据,感觉不是很好
7 楼 25707332 2010-08-04  
mathfox 写道
太复杂了,没有看

不过Oracle有dblink这个东西,能支持两个数据库一起查。


!
那我就不知道为什么项目经理叫我这样做了。。。

6 楼 mathfox 2010-08-04  
太复杂了,没有看

不过Oracle有dblink这个东西,能支持两个数据库一起查。
5 楼 25707332 2010-08-04  
抛出异常的爱 写道
25707332 写道
抛出异常的爱 写道
一条sql

在不同的数据库上,这2个数据库没在一台电脑上。。。

没注意到.

pagesize 与pag 200什么关系 ?


没啥关系。。。

200 根据a的id查询b时的条数上限  就是下面那个sql里面的in  最多允许200个参数  那个pagesiz可大可小  表示一次查多少条a
4 楼 抛出异常的爱 2010-08-04  
25707332 写道
抛出异常的爱 写道
一条sql

在不同的数据库上,这2个数据库没在一台电脑上。。。

没注意到.

PS:pagesize 与SELECT_IN_NUMBER =200什么关系 ?

  temp.clear();  这个不应该放到方法内部
会产生阅读中断
3 楼 25707332 2010-08-04  
抛出异常的爱 写道
一条sql

在不同的数据库上,这2个数据库没在一台电脑上。。。
2 楼 抛出异常的爱 2010-08-04  
一条sql
1 楼 17721499 2010-08-04  
有点复杂,看不懂,晕。

相关推荐

Global site tag (gtag.js) - Google Analytics