`
BBjava
  • 浏览: 126434 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

jdbc大数据量查询优化(转)

阅读更多

大略的看了一下觉得编好文章:http://hi.baidu.com/lovemywolf/blog/item/b2e3a912e22e67cfc2fd78a3.html
在实际的任何一个系统中,查询都是必不可少的一个功能,而查询设计的好坏又影响到系统的响应时间和性能这两个关键指标,尤其是当数据量变得越来越大时,于是如何处理大数据量的查询成了每个系统架构设计时都必须面对的问题。本文将从数据及数据查询的特点分析出发,结合讨论现有各种解决方案的优缺点及其适用范围,来阐述J2EE平台下如何进行查询框架的设计。

Value List Handler模式及其局限性
在J2EE应用中,对于大数据量查询的处理有许多好的成功经验,比如Value List Handler设计模式就是其中非常经典的一个,见图1。该模式创建一个ValueListHandler对象来控制查询的执行以及结果集的缓存,它通过DAO(Data Access Object)来执行查询,并将数据库返回的结果集(传输对象Transfer Object的集合)缓存起来,接下来的客户端查询请求将直接从缓存中获得。它的特点主要体现在两点:服务器端缓存数据,每次只返回客户端本次操作所需的数据,通过这两个措施来减少数据库的访问次数以及增加客户端的响应速度,达到最优的查询效果。当然,这里面隐含一个前提就是客户端采用分页的方式来浏览数据。关于该模式的详细介绍,请参考[Core J2EE Patterns]一书。
http://www.chinaitpower.com/members/ok_winnerboy/1121167368078Value_List_Handler.bmp' />
图1:Value List Handler类图

但是在实际的应用过程中,会发现该模式存在一定的局限性,其实可以说是该模式应用具有一些前提条件:
1、由于缓存是以内存来换性能,这对于小数据量会工作得很好,但是如果结果集很大,内存消耗将会非常严重。同时,消耗在处理结果集上的时间也会越来越长,比如要循环读取记录集中的数据,然后依次填充每个传输对象,想想看几百万条数据这样处理起来肯定让人不能忍受。过长的处理时间不仅降低反应速度,同时还会占用宝贵的数据库连接资源,造成其它地方无连接可用。虽然,在DAO模式中利用CachedRowSet,Read Only RowSet ,RowSet Wrapper List等策略(详见参考资料)来代替Transfer Object Collection策略,有效地提高了处理速度,但是仍然存在着在大集合数据中进行定位、遍历等问题。试想一想,即使在CachedRowSet中的absolute(2000000)也是非常费时的操作。所有这一切的根源就在于缓存是一次性读取所有的数据,虽然有时你可以利用业务逻辑来强制性增加一些限制条件(比如产品查询必须选择大类和次类),但这种限制往往是不牢靠的或者说只是一时的权宜之计。也有人提出,可以不必缓存所有的查询结果,而采取只缓存部分结果集,比如500,1000条,但这样一来,就涉及到复杂的查询数据是否越界的控制,增加了复杂度,同时也不易实现。

2、既然使用缓存,那就不得不面对一个数据更新的问题,使用缓存,实际上就假定了在数据缓存期间,数据库中的数据不会改变,或者这些改变可以不被反映出来。但是,在很多场合下(比如常见的业务系统中)这些数据库中的数据经常会发生变化,而且这些改变需要及时反映给客户端。

3、缓存其实存在一个基本前提,就是缓存的数据会被客户端反复查询使用,具体到分页查询就是客户会选择不同的页数来查看数据。如果客户端的查询条件始终变化,或者用户基本上只关心第一页的数据(仔细琢磨一下用户的习惯,这在很多中应用场合都很常见),那缓存就失去了应有的意义,变得多此一举了。

数据分析
所以说,在决定是否应用某种设计模式前,我们需要对被查询数据的特点以及这些数据以何种方式被使用(查询的特点)进行一个分析,根据不同的结论来决定采用何种处理策略。而且,数据本身的特点和被使用的方式往往交织在一起,需要综合起来考虑,但这其中主要的考量点还是数据查询的特点。

一般来说,可以从以下几个方面来分析数据:
1、     数据量大。
这是我们今天讨论的数据的一个最基本特点,这个特点在查询框架设计时要引起足够的重视。
注意:大数据量的查询是指查询时匹配条件的数据量大,而不是指表中的数据量大,虽然大部分时候这两者都是一致的。因为在某些情况下,业务逻辑可以限制或者只需要一次获取很少量的数据,而查询的表中的数据量却可能很大,那这种情况就不属于本文的讨论范围。
2、     关联复杂,多表关联。
越是简单的数据可能关联越少,而越是复杂的数据往往都是多表关联,这样很多时候你需要将这几张表作为一个整体来考虑。
3、     变化频率。
从这个角度出发,可以大致将数据分为以下几类:几乎不变化的睡眠数据;有规律定时更新的数据,比如招聘网站的职位信息;经常性无规律更新的数据。
4、     成长性。
数据是否具有成长性,要预见数据的成长性,并在现有方案中考虑这种成长性,避免到时候查询框架的重新设计,象大部分的业务数据都具有这种成长性。
注意:这里也要特别注意区分数据本身的成长性和数据查询的成长性,这看似等同的两者其实还是存在很大的区别。就拿招聘网站来说,有效职位的数据肯定是一天天在增加,具有高成长性,但是在某个区间(比如一个月,一个星期)内的有效职位查询则变化不会太大,不具有成长性。而后者却往往是实际系统中最常遇到的查询情况。
5、     数据查询的频率和方式。
所有的数据查询不可能被等同地使用,你要分清楚系统中的几个关键查询,这些查询使用频率高,响应要快。试想一想,如果一个电子商务系统的产品查询每次都要让顾客等上十秒钟,结果就可想而知。

用户的使用习惯分析
除了对数据查询本身需要进行分析之外,我们还需要去分析一下用户如何来使用或者看待这些数据,用户的使用习惯如何。有人可能觉得这作用不大,或者很难去分析,其实查询的最终使用者是用户,他们的一些习惯会很大程度上左右你的设计。

1、     用户关心数据哪些方面的特性,不关心哪些方面的特性。
上面我们分析了数据本身的许多特性,那用户对其中哪些特性最敏感呢?比如说对脏数据特别不能接受,那我们就必须在查询框架设计时特别照顾到这一点。因为再好的框架设计都不可能在每个方面都能达到最优的效果,当必须有所取舍的时候,我们就要明白哪些特性是客户最关心的。

2、     用户如何来使用数据。
现在一般查询的客户端都采用分页的方式,一个查询可能会存在十几页甚至几十页结果。对于某些查询,用户可能往往只关心第一页或者前几页的结果,比如用户需要查询出最近完成的工单,而对于另外一些查询,用户可能对所有页结果都很关注,比如用户查询出最近三天新增的招聘职位。这不同类型的查询在查询框架设计的时候都需要有所考虑并给予不同的处理策略。

查询框架的设计
对数据及用户使用习惯进行了仔细的分析,接下来就可以根据这些分析来设计你的查询框架了。在J2EE架构下,对于大数据量的查询主要采取以下两种方法:
基于缓存的方式:
从数据库得到全部(部分)数据,并将其在服务器端进行缓存,接下来的客户端请求,将直接从缓存中取得需要的数据。这其实就是Value List Handler模式的原理,它主要适用于数据量不是非常大,变化不是很频繁(或者变化频繁但是有规律)且不具有成长性的情况,比如招聘网站或者电子商务网站的大部分查询就非常适合采取这种方式。

采用这种方式,要特别注意第一次查询问题,避免响应性能达不到要求,因为每个查询第一次都需要连接数据库,从中获取数据并缓存起来,所以第一次查询会比接下来的查询都显得更慢一些。

对于数据的缓存,有以下几种实现方式:
     直接缓存在服务器端
Value List Handler模式就采取这种方式,并且可以根据不同的情况采取不同的缓存策略,比如Transfer Object集合,CachedRowSet等,这取决于你的DAO实现策略。
     用临时表来保存查询结果
WLDJ(www.sys-con.com/weblogic/)杂志2004年第7期上有一篇名为“Handling Large Database Result Sets”的文章,它详细介绍了如何利用临时表来改良Value List Handler模式以支持大型的J2EE应用。

当然除了以上这些方法以外,实现缓存也可以求助于操作系统的特定实现,以前我在IBM DW发表过一篇探讨MMF在Java中应用的文章(见参考资料),可惜未有深入,有兴趣的朋友可以参考一下。

在使用Value List Handler模式时,要特别注意以下几点:
1、     该模式一般和DAO模式搭配使用。
2、     该模式有POJO,stateful session bean两种实现策略。
3、     如果采取stateful session bean实现策略,则默认该缓存的时间长度为整个用户会话。

前面我们也提到过,如果数据不是绝对不变的,那缓存就面临更新的问题,一旦更新就可能存在着数据不一致,如果恰巧客户也希望能够看到变化的效果,这个时候就需要采取某种措施来保证这种一致性。常见的措施可以是设置一个标志位,每次发生数据更新后都将其对应的标志位更新,查询时如果发现标志位更新了,就直接从数据库获取数据,而不是从缓存中获取数据。另外一种方式就是数据更新的同时主动去清空session中的缓存,如果采用stateful session bean实现策略的话。

当然,采取缓存方式的大数据量查询一般来说都不大可能遇到设置更新标志位的问题,因为这种应用方式决定了数据不大可能变化,或者数据变化不要求立刻反应给用户。比如招聘网站新增加了一些职位信息,如果这些更新恰巧发生在某些用户的会话期间,且没有设置更新标志位,那这些新增信息就不会反应到用户的查询结果中,这种处理方式也是可以接受的。

基于查询的方式:
不进行数据缓存,客户端的每次数据请求都需要进行实际的数据库查询,这种方式适用于量大,具有成长性,变化频繁的数据。该方式的特点是每次查询的时间都大致相等,不会存在基于缓存的方式的第一次查询问题,但后续的操作会比缓存方式的查询慢一些。采取这种方式的查询框架设计更具有可扩展性以及对数据变化更好的应变能力,在大部分的业务系统中都推荐使用该方式。

使用这种方法,每次查询应该只从数据库获得客户端所需的数据,这样就涉及到如何获得部分数据的问题。一种是查询出符合条件的所有记录,然后遍历该记录集根据上次查询结果来比较记录中的某些字段获取本次查询需要的部分数据,由于要对记录集进行遍历,效率不高,一般都不推荐使用,而往往采用另一种增加sql查询语句条件的方式,这种方式有以下几种实现策略:
     专属于数据库的,比如Oracle的rownum
有些数据库提供了标识查询结果集中行号的功能,利用该标识就可以限定某个范围的记录,比如下面这个方法就是利用Oracle数据库中的rownum功能来包装sql查询语句以获得部分记录集。
private String wrapSQL(String strSql) {
       String strWrapSql = "";
       strWrapSql = "select * from (select rownum mynum, xxx.* from (" + strSql + ") xxx ) where mynum between " + getFrom() + " and " + getTo();
       return strWrapSql;
}

当然这种实现策略的缺点很明显,就是绑定于特定数据库的实现,有的数据库可能并不提供这个功能,不能独立于特定数据库实现而提供一个通用的实现。
     利用业务数据的规律性来获得部分记录集
对于实际系统中的每个查询,都会被指定按照某种方式来进行排序(不管是业务逻辑默认的还是客户端指定的),也就是说查询的sql中存在着order by子句。既然查询结果集对于order by子句中的字段会呈现一定的规律性,那我们是不是可以利用这种有序性来获得部分的记录集呢?答案是肯定的。

参考资料中的Paging in J2EE一文对这种方式进行了详细的阐述,并且给出了一个实例,有兴趣的朋友可以参考一下。但是,该篇文章针对的是那些排序方式是业务逻辑默认的情况,即排序字段可以通过属性文件定义下来的。但在实际情况中,还存在另外一种可能,排序方式是由客户端指定时,这个时候排序的字段和升降序都由客户端指定,这个时候就需要在查询框架中提供某种机制来将排序字段和where子句进行映射。

获得结果集的总数
当采用获取部分记录集的方式时,获得的记录集大小并不能真实地反映出查询结果集的大小(因为只是其中的一部分),所以又涉及到对于记录总数的处理:
     如果不需要,或者没有意义,不提供,因为count(*)操作耗资源
     在sql语句中获得
在有些情况下,你可以在sql语句中直接加上count(*)来获得记录集总数,比如你用Statement接口的setMaxRows(int max)方法来控制返回的记录数时。
     单独进行一次查询
为count(*)语句单独进行一次数据库查询,只要将构造好的sql语句中的select字段替换为count(*)即可。当然count(*)的查询sql应该尽量简练,不要加上order by子句之类的。

其实,以上提到的这两种方式在实际的查询框架设计时不可能完全分开来,只不过以哪种方式为主而已,它们往往是混合在一起使用以达到最优的查询效果。

查询框架设计时的注意点
当然,在优化大数据量查询的过程中,数据库本身的优化必不可少,比如建立索引等措施。但是,数据库的优化要循序渐进,要和程序代码的优化相一致。在进行数据库优化之前,要做的是sql语句的优化,这可以通过观察它的执行计划来进行考量。这些工作最好都由DBA来参与共同完成。

对于性能的个人态度:要提早考虑,确定一个方案前要进行测试,并预见未来的变化。

对于“大数据量”这几个词不要只停留于口头的重视,要想有切身的体会,最好在框架设计好后,进行大数据量的模拟压力测试,实际地检验你的设计。不要总是回答:我想,应该没有问题,应该支持几百万的数据,最好拿出最有说服力的数据。

以后关注的方向:
1、     EJB 3.0规范中EJB QL语法得到加强,比如可以支持原生SQL语句,使得利用实体Bean来实现部分类型查询成为可能。
2、     JDBC规范是否能够在某些方面对这种大数据量查询提供某些功能接口,比如获得部分记录集,以及统一的缓存接口。

总结
本文给出了查询框架设计时的一些思路和方法,最重要的一点就是在设计之前要充分研究你的目标系统,了解最终的数据查询特点。由于本文只是个人的一些观点和经验总结,偏颇之处在所难免,希望有兴趣的朋友一起来讨论这个问题。

参考资料:
1、     [Core J2EE Patterns] Core J2EE Patterns: Best Practices and Design Strategies, Second Edition, By Deepak Alur, John Crupi, Dan Malks.
2、     关于DAO模式中CachedRowSet等策略的详细讨论,请参考[Core J2EE Patterns]一书。
3、     Paging in J2EE: Manage Large Result Sets Efficiently. By Lara D'Abreo.
(http://www.devx.com/Java/Article/21383)
4、     如何提高系统性能指标??MMF在Java中的应用
(http://www-900.ibm.com/developerworks/cn/java/l-java-performance/index.shtml)
分享到:
评论

相关推荐

    spark jdbc 读取并发优化

    这种模式在数据量不大时不会造成问题,但在数据量级达到千万甚至亿级别时,效率低下,容易产生内存溢出(OOM)错误。例如,对于千万级别的数据表,执行count操作可能需要等待极长的时间,因此不推荐使用默认的单...

    优化JDBC方法功略

    相反,可以创建一个不会返回任何数据的“哑元”查询(如“SELECT * FROM UnknownTable WHERE 1 = 0”),然后获取其ResultSet的MetaData,这样可以减少网络传输的数据量,提高性能。 除此之外,还有其他一些优化...

    JDBC性能优化.pdf

    1. **背景**:在大数据量的场景下,盲目地获取所有列会显著增加数据传输量,从而降低应用程序性能。 2. **建议**:根据业务需求精确指定所需的列名,使用`ResultSet`的`getColumnIndex(String columnName)`等方法...

    多线程以JDBC的方式返回海量数据

    然而,当数据量非常大时,一次性加载所有数据可能会导致内存溢出,降低应用程序性能。 为了解决这个问题,我们可以采用多线程技术。多线程允许程序同时执行多个任务,提高资源利用率和程序响应速度。在处理海量数据...

    JavaWeb+JSP+Servlet+JDBC分页查询和查询后分页界面优化最终版

    这不仅提高了性能,还减少了网络传输的数据量。 2. **Servlet**:在分页查询的流程中,Servlet通常扮演控制器的角色。它接收来自客户端的请求(如页码、每页显示数量),调用业务逻辑处理分页查询,然后将结果传递...

    如何避免JDBC引起的内存溢出情况

    - **分批处理**:可以考虑将大查询分成多个小查询来执行,每个查询只处理一部分数据。 - **关闭资源**:确保在使用完PreparedStatement和ResultSet之后及时关闭它们,释放资源。 - **使用流式处理**:如果可能的话,...

    应对sharding-jdbc结合mybatis实现分库分表功能 分表的联合查询采用将mysql的数据同步到elasticsearch进行筛选

    在IT行业中,数据库扩展是解决高并发、大数据量场景下的常见问题。Sharding-JDBC是阿里巴巴开源的一款轻量级的数据库中间件,它允许开发者在...这样的设计思路在大数据量的场景下,能够提高系统的查询效率和整体性能。

    JDBC访问数据库的优化策略

    然而,随着用户数量的增长和数据量的增加,如何优化JDBC访问数据库的方式,提升网络数据库应用系统的性能,成为开发者关注的焦点。 #### 二、数据库的连接策略 ##### 1. 选择合适的连接模型 连接模型的选择对于...

    JDBC-MySQL分页查询

    在Java开发中,JDBC(Java Database Connectivity)是用于与各种数据库进行交互的标准接口,而MySQL则是一种广泛使用的开源关系...确保在实际应用中考虑性能优化,如避免过多的数据库查询,以及使用索引提高查询效率。

    JDBC数据同步

    例如,在电商系统中,商品库存信息可能分布在不同的数据库中,通过JDBC数据同步,可以确保所有数据库的商品库存状态实时更新,防止超卖情况发生。 ### JDBC基础 JDBC是Java API,提供了一套标准的接口和类,用于...

    处理大数据量excel

    使用Java和SQL Server JDBC驱动处理大数据量Excel文件的一般步骤可能包括以下步骤: 1. **读取Excel**:使用Apache POI等库读取Excel文件,可以逐行或按需加载数据,避免一次性加载整个文件导致内存溢出。 2. **...

    Stringbuffer JDBC 模糊查询

    在性能优化方面,如果需要对大量数据进行模糊查询,可以考虑使用索引、分页查询以及数据库特有的优化技术,以提高查询效率。同时,合理使用`StringBuffer`能有效减少内存分配,提升程序性能。 总结来说,`...

    jdbc实现动态查询与分页查询的结合

    在实际项目中,可能还需要考虑性能优化,如使用连接池管理数据库连接,以及使用批处理处理大量数据等。 在提供的压缩包文件"DongTQuerryAddPageTest"中,可能包含了一个具体的示例代码,你可以参考这个代码进一步...

    优化JDBC性能的三大技巧.doc

    标题中的“优化JDBC性能的三大技巧”是指在开发基于Java的JDBC应用程序时,为了提升性能而采取的关键策略。这些技巧主要集中在正确使用数据库MetaData方法、只获取必要的数据以及选择最佳性能的功能,并且有效管理...

    JDBC数据源连接池的配置和使用示例

    **JDBC数据源连接池配置与使用详解** 在Java Web应用中,数据库连接的管理是至关重要的。为了提高性能和资源利用率,开发人员通常会使用数据源连接池。本篇文章将详细讲解JDBC数据源连接池的配置和使用,以帮助你更...

    jdbc分页查询源码

    同时,对于大数据量的场景,还可以考虑使用缓存技术,如Redis,将部分数据缓存到内存中,减少对数据库的依赖。 总的来说,JDBC分页查询是Java数据库操作中的常见需求,通过合理设计和优化,可以有效提升应用的性能...

    Access_JDBC30.jar(不限查询次数(50)及每次查询的条数(1000))

    总的来说,"Access_JDBC30.jar"是一个优化过的Access数据库连接驱动,它为Java开发者提供了一个高效、无限制的接口来访问和操作Access数据库,极大地提升了开发效率和应用性能。使用这个驱动,开发者能够更自由地...

    java excel大数据量导出demo,可支持模板导出

    这个"java excel大数据量导出demo"提供了一个高效、灵活的解决方案,它支持模板导出,分页查询,以及处理大数据量的能力。在这个项目中,开发者采用了Apache POI库的SXSSFWorkbook子类,这是一个专门为处理大量数据...

    jdbc jdbc jdbc

    9. **性能优化**:JDBC提供了多种优化技巧,如关闭不再需要的资源(如结果集、Statement和连接),使用连接池来重用连接,以及合理使用`PreparedStatement`避免SQL注入。 10. **JDBC API的扩展**:随着技术的发展,...

    jdbc-批量插入数据

    批量插入数据是数据库操作中常见的一种优化策略,特别是在数据分析、数据迁移或大数据处理等场景。传统的单条插入方式可能会导致大量的网络往返和数据库事务开销,而批量插入则通过一次提交多个SQL语句来减少这些...

Global site tag (gtag.js) - Google Analytics