网上看到的一篇文章,结合自己的经验和大家交流一下
对大量数据的分页处理
问题描述:
背景1:一客户通过IE请求Web服务器查询数据,而查询结果是上千条甚至是上万条记录,要求查询结果传送到IE客户端并分页显示。
背景2:一客户通过IE或者其他方式请求Web服务器查询数据,而查询结果是上千条甚至是上万条记录,并要求查询结果把包传送到客户的E-mail中。
问:对于这样的有大量数据的结果集,在Web服务器端如何有效的处理?
可能涉及到的问题:
1. 内存占用
大量数据的结果集,可能要占用非常大的内存
2. 传输速度及策略
具体的分页处理技术
处理方法
1 游标查询 直接使用ResultSet来处理。
ResultSet是直接在数据库上建立游标,然后通过ResultSet的行位置定位接口来获得指定行位置的记录。
当用户第一请求数据查询时,就执行SQL语句查询,获得的ResultSet对象及其要使用的连接对象都保存到其对应的会话对象中。
以后的分页查询都通过第一次执行SQL获得的ResultSet对象定位取得指定行位置的记录。
最后在用户不再进行分页查询时或会话关闭时,释放数据库连接和ResultSet对象等数据库访问资源。
说明:
在用例分页查询的整个会话期间,一个用户的分页查询就要占用一个数据库连接对象和结果集的游标,这种方式对数据库的访问资源占用比较大,并且其利用率不是很高。 所有的数据库产品。
优点:
减少了数据库连接对象的多次分配获取,减少了对数据库的SQL查询执行。
缺点:
占用数据库访问资源-数据库连接对象,并占用了数据库上的资源-游标。而这些资源都是十分宝贵的有限制的。
结论:
这种的数据库查询分页处理方式不是最佳的。一般不适用这种方式。
2 定位行集SQL查询 主要是直接使用数据库产品的提供的对查询的结果集可定位行范围的SQL接口技术。
在用户的分页面查询请求中,每次可取得查询请求的行范围的参数,然后使用这些参数生产取得指定行范围的的SQL查询语句,然后每次请求获得一个数据库连接对象并执行SQL查询,把查询的结果返回给用户,最后释放说有的数据库访问资源。
说明:
这种方式需要每次请求时都要执行数据库的SQL查询语句;对数据库的访问资源是使用完就立即释放,不白白占用数据库访问资源。 对特定(提供了对查询结果集可定位功能的)的数据库产品。
如:Oracle,DB2, PostgreSQL,mySQL等。(MS SQL Server 没有提供此技术。) 如:
1. Oracle数据库使用关键字:rowid或rownum
2. DB2:
rowid或rownum ()
3. PostgreSQL 使用LIMIT 和 OFFSET
4. MySQL 使用Limit
优点:
这种技术是直接使用数据库产品自己提供的可对查询结果集定位行范围过滤的功能,因此直接利用了数据库的性能对此分页查询的优化功能。
对数据库的访问资源(数据库连接对象,数据库游标等)没有浪费,这些资源的充分重复的利用。
对查询的结果对Web容器没有什么特别要求。
缺点:
要执行多次数据库SQL查询操作。对每次的分页面操作请求都要指定相应范围的结果集来执行SQL语句的数据库查询操作,这对数据库有一定的影响。
对每次分页面查询请求要频繁的从Web容器中获得数据库访问资源(数据库连接对象和数据库游标)。
要依赖于具体的数据库产品。因为对没有实现没有提供此技术的数据库产品不能使用此方式。
结论:
由于每次对数据库的SQL查询操作相对而言耗用的数据资源比较少,并且在实际用户的操作中,有可能用户对查询的所有结果集只是需要查看其中的部分页面。因此这种方式是最佳的。
3 特别处理的定位行集SQL查询 这种方式是在方式2的基础上针对不提供对查询结果集行范围定位的数据库产品。
其在Web容器端的操作逻辑大致和方式2相同。
只是先要对要查询的数据库表要有一字段的数据能区别每条不同的数据记录。第一次查询时,获得用来可唯一标识不同记录的字段的所有结果集,并缓存起来以备后面的分页面查询指定要查询的结果集的行范围。 主要是针对不同对查询行集可定位范围获得的数据库产品,如MS SQL Server等。 假设从A,B,C三个表中选取数据。且A有字段ID用来可唯一区别不同的记录。
那么第一次查询的时候,会查询两次
1. select A.id from A,B,C where condition.
2. 把A的ID缓存到SESSION中?3.从Session中。现可按照次序来取得相应页面范围的ID来,并构造下一个查询语句:select A.name, B.add from A,B,C where condition && ( A.ID in 本页面范围的 ID )
以后每次翻页的时候,依次获得对应页的ID只要表中唯一的就可以了。无所谓大小,顺序?这样,SESSSION缓存的就只是一列而不是所有列了。当然,对于列数不多的,效果并不好。
也可使用存储过程实现,可参照:http://expert.csdn.net/Expert/topic/2365/2365596.xml?temp=.7529261
优点:
同方式2
缺点:
同方式2;
还要在要查询的数据库表中建立一个相应的ID,用来唯一区别每条记录。
结论:
同方式2。
4 缓存一次SQL查询的结果集
优点:
缺点:
既然我们要缓存结果,那么用户就可能会看到过期的数据
说明:
对于实际情况的应用来说,一般结合实际情况,结合使用方式2(或方式3)和方式4。如:一个应用场景:对公司的产品的查询是经常的,但是产品的种类不是很多,这时可使用缓存方式;但是对有些查询结果集较大,数据库和Web容器之间的网络访问由可能是远程的,这时候可考虑使方式2(或者方式3)。
//-----------------------------------------------------------
以前做的几个项目中,大多数都是采用类似于方式1的分页。如果查询频繁的话,系统访问速度就是问题。
方法2确实是个好方法,但是过于依赖数据库,并非文章中所提及的“最佳方法”。
个人认为方法1和方法2相结合,才能达到较好的效果。即先查询出较大的结果集(可能是最大集合的1/100),然后对该结果集使用方法1的分页。这样既不用频繁的访问数据库,也不会创造出过多的对象。当然正如文章中提到的:MS SQL Server 不支持定位技术 >_< ,所以游标的定位需要靠程序解决。
方法3好象是方法4的补充了。。。。研究ing
下面是方法4的实现
//关于分页显示中,因对象过多而造成的系统速度过慢问题的修改,适合数据库更新不频繁的情况
//得到一个idList
List idList = new ArrayList();
rs = select id from db where condition
while (rs.next){
idList.add(rs.getString("id"));
}
//根据idList得到formList
List formList = TEST.getInstance().getInfo(idList);
//类TEST中具体实现
//创建唯一的instance,只有当系统第一次调用此方法,则创建实例
Map m = new Treemap();
rs = select * from db
while (rs.next){
TestForm testForm = new TestForm();
testForm.setvalue(...);
m.put(TestForm.getF_id(),testForm);
}
//根据idList从map中取得相应数据
public List getInfo(List idList){
List infoList = new ArrayList();
for (int i = 0;i
String id = idList.get(i);
infoList.add(m.get(id));
}
return infoList;
}
//显示formList中的内容
//显示ListForm中的具体内容
不足之处,多多指教。-_-!