Hibernat性能优化.doc_第1页
Hibernat性能优化.doc_第2页
Hibernat性能优化.doc_第3页
Hibernat性能优化.doc_第4页
Hibernat性能优化.doc_第5页
已阅读5页,还剩15页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

性能、规模、风险 初评hibernate来源: LUPA开源社区发布时间: 2007-05-27 03:59 版权申明 字体: 小 中 大 文章来源于1,Hibernate究竟能用到多大的项目?什么是多大,这很难说,我想应该是数据库表比较多,业务逻辑比较复杂(表关联复杂),而且要求性能比较高吧。其他行业我不知道,电信行业软件都该是如此。据我了解,这样的数据库访问(尤其是频繁发生的交易)通常需要用存促过程来解决,甭说自己写java的JDBC了,更不用提用Hibernate来解决。所以肯定有个适用范围的问题,希望已经熟练hibernate,并做个一些项目的人的人给予帮助解决2 Hibernate性能会多好?很多人都说Hibernate性能好,无非是它的一些cache功能,姑且不讨论是否有这样的最佳实践(希望国内有人能提出best practices,或者pattern),单从cache本身来说,也并不是有了就好,否则,也不会出现分布式cache。就像长辈成天唠叨这,唠叨那,我表面认为那是对你好,其实你自己心里甭说又多烦恼了。hibernate为了解决方方面面的性能,而提供了方方面面的特性,但这些加在一起就不是好事情了3 DBA赞同在项目中使用Hibernate么?我不是DBA,甚至对写sql语句都不熟悉,所以有时候写DAO的时候经常请教DBA,DBA也很习惯的在DAO中找到他们熟悉的SQL(而不是HQL),我想这是DBA不提倡Hibernate的一个原因。另外一个原因是在于数据库本生就是一个十全十美的系统,无论其性能还是伸缩性(我想没人反对我的观点吧),因此DBA深信,能把问题交给数据库是最好的。因此他们不会赞同一切都依赖Hibernate。这个观点也希望懂Hibernate的DBA给予证实EJB的历史使不少人都以怀疑的态度来看待Hibernate,以前EJB怎么成功的,Hibernate也在走这样的路,所以,我不太相信评测,只是希望一且能从实践出发,让hibernate能减少开发人员工作量,以及减少用它的风险。评论hibernate 最终还是用 jdbc ,看上去效率比jdbc低,但是事实不一定,要看怎么优化了 数据库设计 粒度控制 一二级缓存的高级配置 对象检索的策略 都能影响到性能问题啊 我觉得hibernate 开发效率高, 假如用不好的话,会多很多无用的sql,不优化好,性能绝对比不过jdbc的。 不过直接jdbc性能是好点,特别是数据量大的时候。可以直接优化sql,但是开发速度慢。我觉得jdbc 和 hibernate 的比较,看你需要什么操作了, 再级联关系较多的时候,用hibernate 应该比 jdbc 强, 特别是检索主方,我个人认为在hibernate在删除多的那一方的一条数据时太复杂! 当然各有好处,就看你怎么样设计数据库,然后选择怎么用!Hibernate 性能优化有很多人认为Hibernate天生效率比较低,确实,在普遍情况下,需要将执行转换为SQL语句的Hibernate的效率低于直接JDBC存取,然而,在经过比较好的性能优化之后,Hibernate的性能还是让人相当满意的,特别是应用二级缓存之后,甚至可以获得比较不使用缓存的JDBC更好的性能,下面介绍一些通常的Hibernate的优化策略: 1.抓取优化 抓取是指Hibernate如何在关联关系之间进行导航的时候,Hibernate如何获取关联对象的策略,其主要定义了两个方面:如何抓取和何时抓取 1)如何抓取。 Hibernate3主要有两种种抓取方式,分别应用于对象关联实例(many-to-one、one-to-one)和对象关联集合(set、map等),总共是四种变种 JOIN抓取: 通过在SELECT语句中使用OUTER JOIN来获得对象的关联实例或者关联集合) SELECT抓取: 另外发送一条SELECT语句来抓取当前对象的关联实体和集合 在我的开发经历中,此处对性能的优化是比较有限的,并不值得过多关注 例: A.应用于对象关联实例(默认是false) B.应用于对象关联集合(默认是auto) . 2)何时抓取 主要分为延迟加载和立即抓取,默认的情况下Hibernate3对对象关联实采用延迟加载,普通属性采用立即抓取,通过延迟加载和采用适当的抓取粒度,与不采用优化相比往往可以将性能提升数倍 立即抓取:当抓取宿主对象时,同时抓取其关联对象和关联集以及属性 延迟加载:当抓取宿主对象时,并不抓取其关联对象,而是当对其对象进行调用时才加载 例: A.应用于对象关联实例(默认是延迟加载) B.应用于对象关联集合(默认是延迟加载) . 对于延迟加载,需要注意的时,对延迟对象的使用必须在Session关闭之前进行,Hibernate的LazyInitalizationException往往就是由于在Session的生命期外使用了延迟加载的对象。当我们进行Web开发时,可以使用OpenSessionInView模式,当请求开始时打开session,当请求响应结束时才关闭session,不过,在使用OpenSessionInView模式时,需要注意如果响应时间比较长(业务比较复杂或者客户端是低速网络),将Session资源(也就是数据库的连接)占用太久的话可以会导致资源耗尽 3)抓取粒度 抓取粒度指的是对象在关联关系之间被导航时一次预先加载的数量,Hibernate程序的性能比较差往往就在于没有对抓取粒度仔细考虑,当加载一个列表并在列表中的每个对象中对其关联进行导航时,往往导致N+1条SQL语句查询。 例: A.应用于对象关联实例(默认为1),注意,对对象关联实例的设置是在被关联的对象之上的,譬如 class User Group g; 那么抓取粒度应该在Group的配置文件之上,见下 . 对该值并没有一个约定俗成的值,根据情况而定,如果被关联表数据比较少,则可以设置地小一些,3-20,如果比较大则可以设到30-50,注意的时候,并不是越多越好,当其值超过50之后,对性能并没有多大改善但却无谓地消耗内存 假设有如下例子: List users = query.list(); 如果有20个User,并对这20个User及其Group进行遍历,如果不设置batch-size(即batch-size=1),则在最糟糕的情况 下,需要1 + 20条SQL语句,如果设置batch-size=10,则最好的情况下只需要1 + 2条SQL语句 B.应用于对象关联集合(默认为1) . 2.二级缓存 Hibernate对数据的缓存包括两个级:一级缓存,在Session的级别上进行,主要是对象缓存,以其id为键保存对象,在Session的生命期间存在;二级缓存,在SessionFactory的级别上进行,有对象缓存和查询缓存,查询缓存以查询条件为键保存查询结果,在SessionFactory的生命期间存在。默认地,Hibernate只启用一级缓存,通过正确地使用二级缓存,往往可以获得意想不到的性能。 1)对象缓存: 当抓取一个对象之后,Hiberate将其以id为键缓存起来,当下次碰到抓取id相同的对象时,可以使用如下配置 方法1:在缓存对象上配置 useage表示使用什么类型的缓存,譬如只读缓存、读写缓存等等(具体参见Hibernate参考指南),值得注意的时,有部分缓存在Hibernate的实现中不支持读写缓存,譬如JBossCache在Hibernate的实现中只是一种只读缓存,具体缓存实现对缓存类型的支持情况,可以参见org.hibernate.cache包 regions表示缓存分块,大部分的缓存实现往往对缓存进行分块,该部分是可选的,详细参见各缓存实现 方法2:在hibernate.cfg.xml中配置 我认为第二种更好,可以统一管理 2)查询缓存 查询时候将查询结果以查询条件为键保存起来,需要配置如下 A.在hibernate.cfg.xml中配置(启用查询缓存) true (前面的属性名可参见常量org.hibernate.cfg.Enviroment.USE_QUERY_CACHE) B.程序 query.setCacheable(true); query.setCacheRegions(.); 需要注意的是,查询缓存与对象缓存要结合更有效,因为查询缓存仅缓存查询结果列表的主键数据 一般情况下在开发中,对一些比较稳定而又被频繁引用的数据,譬如数据字典之类的,将其进行二级缓存,对一些查询条件和查询数据变化不频繁而又常常被使用的查询,将其进行二级缓存。由于二级缓存是放在内存中,而且Hibernate的缓存不是弱引用缓存(WeekReference),所以注意不要将大块的数据放入其中,否则可能会被内存造成比较大的压力。 3.批量数据操作 当进行大批量数据操作(几万甚至几十几百万)时,需要注意两点,一,批量提交,二,及时清除不需要的一级缓存数据 1)所谓的批量提交,就是不要频繁使用session的flush,每一次进行flush,Hibernate将PO数据于数据库进行同步,对于海量级数据操作来说是性能灾难(同时提交几千条数据和提交一条数据flush一次性能差别可能会是几十倍的差异)。一般将数据操作放在事务中,当事务提交时Hibernate自动帮你进行flush操作。 2)及时清除不需要的一级缓存数据:由于Hibernate默认采用一级缓存,而在session的生命期间,所有数据抓取之后会放入一级缓存中,而当数据规模比较庞大时,抓取到内存中的数据会让内存压力非常大,一般分批操作数据,被一次操作之后将一级缓存清除,譬如 session.clear(User.class) 4.杂项 dynamic-insert,dynamic-update,动态插入和动态更新,指的是让Hibernate插入数据时仅插入非空数据,当修改数据时只修改变化的数据,譬如对于 class User id username password 如果u.id=1, u.username=ayufox,u.password=null,那么如果不设置动态插入,则其sql语句是insert into users(id, username, password) values (1, ayufox, ),如果设置则其sql语句是insert into users(username) valeus(ayufox) 在如上的情况下,如果修改u.password=11,那么如果不设置动态更新,则sql语句为update users set username=ayufox, password=11 where id = 1,如果设置则为update user set password=11 where d = 1 设置是在class的映射文件中,如下 该设置对性能的提升比较有限Hibernate性能调优一、inverse = ? inverse=false(default) 用于单向one-to-many关联 parent.getChildren().add(child) / insert child parent.getChildren().delete(child) / delete child inverse=true 用于双向one-to-many关联 child.setParent(parent); session.save(child) / insert child session.delete(child) 在分层结构的体系中 parentDao, childDao对于CRUD的封装导致往往直接通过session接口持久化对象,而很少通过关联对象可达性二、one-to-many关系 单向关系还是双向关系?parent.getChildren().add(child)对集合的触及操作会导致lazy的集合初始化,在没有对集合配置二级缓存的情况下,应避免此类操作 select * from child where parent_id = xxx; 性能口诀:1. 一般情况下避免使用单向关联,尽量使用双向关联 2. 使用双向关联,inverse=“true” 3. 在分层结构中通过DAO接口用session直接持久化对象,避免通过关联关系进行可达性持久化三、many-to-one关系 单向many-to-one表达了外键存储方 灵活运用many-to-one可以避免一些不必要的性能问题 many-to-one表达的含义是:0.n : 1,many可以是0,可以是1,也可以是n,也就是说many-to-one可以表达一对多,一对一,多对一关系 因此可以配置双向many-to-one关系,例如: 1. 一桌四人打麻将,麻将席位和打麻将的人是什么关系?是双向many-to-one的关系四、one-to-one 通过主键进行关联 相当于把大表拆分为多个小表 例如把大字段单独拆分出来,以提高数据库操作的性能 Hibernate的one-to-one似乎无法lazy,必须通过bytecode enhancement五、集合List/Bag/Set one-to-many1. List需要维护index column,不能被用于双向关联,必须inverse=“false”,被谨慎的使用在某些稀有的场合 2. Bag/Set语义上没有区别 3. 我个人比较喜欢使用Bag many-to-many 1. Bag和Set语义有区别 2。 建议使用Set六、集合的过滤 1. children = session.createFilter(parent.getChildren(), “where this.age 5 and this.age 100000 order by sharable_mem ; 上面的sql语句是查询shared pool中占用内存超过100K的sql语句。 这个sql可以非常有效的检查出Oracle shared pool中那些严重占用内存的sql,根据我的经验,绝大多数有问题的sql语句都会在这里留下痕迹,通过在这里找出有问题的sql语句并进行修改,再反复运行这个sql脚本,直到所以有问题的sql都处理完毕,这就是对Oracle数据库在sql上面的最好的优化,可以保证不会因为程序员的sql语句问题导致Oracle数据库的性能问题。一个常识问题,却很容易被忽略。比如: select * from table_name where id = 1; select * from table_name where id = 2; 对于这种带参数的sql,id = ? 这个地方叫做站位符(Placeholder)。 拿Java为例,很多人是这样写代码的: String sql = select * from table_name where id = ; Statement stmt = conn.createStatement(); rset = stmt.executeQuery(sql+1); . rset = stmt.executeQuery(sql+2); 这种写法,对于Oracle数据库来说,完全就是两条不同的sql语句, select * from table_name where id = 1; select * from table_name where id = 2; 每次查询都要进行sql语句的执行解析,并且每个sql都会分配一个区域来存放sql解析后的二进制可执行代码。试想,要是id不同的10万个sql呢?Oracle就会分配10万个sql区域来分别存放10万个这样的id不同的sql语句。对于一个数据库驱动的Web网站这样情况下,SGA开的再大,也会很快被耗尽share pool的,最后报一个ORA-4031错误。数据库就连接不上了,只好重起。 有人会想到,Oracle会在Shared Pool不够的时候,使用LRU算法清除掉一些不用的sql,但是遗憾的是,一旦Oracle的访问十分频繁的情况下,清除的速度远远跟不上加进来的速度,会很快导致数据库崩溃。正确的写法应该是: PreparedStatement pstmt = conn.prepareStatement(select * from table_name where id = ?); pstmt.setInt(1,1); rset = pstmt.executeQuery(); . pstmt.setInt(1,2); rset = pstmt.executeQuery(); 这样Oracle数据库就知道你实际上用的都是同一条sql语句,会以这样的形式: select * from table_name where id = :1 解析执行后存放在sql区域里面,当以后再有一样的sql的时候,把参数替换一下,就立刻执行,不需要再解析sql了。既加快了sql执行速度,也不会占有过多SGA的share pool。 可惜的是,很多程序员明知道这个问题,却意识不到问题的严重性,因为上面那种写法,编程的时候很灵活,sql语句可以动态构造,实现起来很容易,后面那种写法,sql语句是写死的,参数不能再变了,编程经常会非常麻烦。 很多数据库的性能问题都是这样造成的。 有兴趣在一个生产系统中,用上面sql检查一下,看看是否选择出来的是否l有很多都是一样的sql语句,只是参数不同,如果是这样的话,就说明程序员的代码写的有问题。Hibernate实际上总是向数据库发送PreparedStatement,已经在很大程度上避免了程序员对于Statement的误用。但是即使是PreparedStatement,不用Placehold,非要把常量带入,也会出现同样的性能问题。当where子句的条件不确定的时候,使用PreparedStatement是非常痛苦的,因为Statment可以简单的用常量代入的方式动态构造sql,而PreparedStatement的的set参数的方法是按照数字索引的,比如:setString(1,.);setBoolean(2,.);就造成了动态构造的sql,你无法确定参数的1,2这样的数字顺序,除非使用很大很麻烦的if else嵌套才能够勉强解决。但是Hibernate好在可以使用带名的Placehold,就是这样:select * from table where user = :name然后set参数的时候,就可以setString(name,.);由于set参数和顺序无关,就很容易实现动态构造sql。这也是Hibernate的一个很大的优点。看了robbin关于PrepareStatment和Statment的解释,受益良多!但我觉得Query q = ses.createQuery(from r in class + Role.class + order by name);这一句并不需要用Placeholder吧,这里的Role.class相当于一个表的名字,而sql查询的时候表名一般是不需要用占位符。就好像你举例中的table_name并不需要用问号来置换一样吧。另外还有一些Hibernate查询的问题请教:Organization org = (Organization)session.load(Organization.class, orgId);这样的性能和Query q = session.createQuery(from org in class + Organization.class + where org.id = ?);/./find method哪一个高?另外from alias_name in class * . 和from * (as) alias_name .这两种方式Hibernate解释是否一样?性能一样?hibernate中的select和from的性能比较呢?对于slsb,是否指无状态回话bean,是否专门作个bean,把所有和事务相关的操作都放在一起?SLSB Stateless Session Bean就是完全用SLSB来实现整个业务层逻辑主题:O/R Mapping性能我不知道为什么你这么怀疑O/R Mapping的性能,O/R Mapping的性能再差也比CMP强吧。 JDO只是一个标准,每个厂商实现的性能各有不同,不好评价。 Apache OJB的性能如何,Apache网站上面有评测。 Hibernate的性能我是花了点时间去研究的。Hibernate可以通过修改配置文件把所有的SQL语句都输出出来,你写一些测试代码观察一下输出的SQL,就什么都明白了。 简单的来说,Hibernate的性能比一个普通的Java程序员写的JDBC代码性能高非常非常多。原因是因为Hibernate本质上还是包装了JDBC来进行数据库操作的,由于Hibernate在调用JBDC上面是非常非常绞尽脑汁的优化JDBC调用,并且尽可能的使用最优化的,最高效的JDBC调用,所以性能相当令人满意。 简单来说吧,对于insert操作,普通JDBC程序员这样来写: Java代码 1. pstmt=conn.prepareStatement(insertintotable1values(?,?);); 2. for(inti=0;inames.length;i+); 3. pstmt.setString(1,names); 4. pstmt.setString(2,pass); 5. pstmt.executeUpdate(); 6. pstmt = conn.prepareStatement(insert into table1 values(?,?););for (int i=0; i names.length; i+); pstmt.setString(1,names);pstmt.setString(2,pass);pstmt.executeUpdate();如果批量插入n条记录的话,那么就是n次向数据库发送SQL语句。而Hibernate则是采用了JDBC2.0的batch功能。 Java代码 1. for(inti=0;inames.length;i+); 2. pstmt.setString(1,names); 3. pstmt.setString(2,pass); 4. pstmt.addBatch(); 5. 6. pstmt.executeBatch();for (int i=0; i names.length; i+); pstmt.setString(1,names);pstmt.setString(2,pass);pstmt.addBatch();pstmt.executeBatch();只是1次向数据库发送SQL,性能高下判若分明。Update操作类似。 select操作,可以使用JCS缓冲查询结果,重复查询比JDBC肯定要快很多,分页操作可以使用数据库的分页sql,或者使用JDBC2.0的scroll result。 另外Hibernate总是尽量延迟向数据发送SQL,它会先把SQL语句缓冲在Session的缓冲区里面,最后在flush的时候一次性的向数据库发送。 总体来说,当你使用Hibernate的时候,相当于有一个JDBC高手来帮你优化JDBC调用,那点封装了一层的损失可以忽略不计。 如果你非要认为Hibernate封装了JDBC会损失性能,那么我问你,Java设计的目标是组件可复用,这些不全都是封装吗?难到你调用组件都损失性能了,如果是那样,还不如不要写什么组件,全部都重新写原始的实现代码呢! 不过如果一个真正的JDBC高手,完全自己优化JDBC代码写DAO实现,我相信会比Hibernate性能高一些,但是Hibernate已经够优化了,你能够做的是非常有限的。就好比用C语言写的程序,虽然比汇编语言差一点,但是差的非常有限。Hibernate vs JDBC就相当于C vs Assemble,除非特殊需要,Hibernate已经足够好了。Hibernate 批量删除,批量更新跟JDBC相差较大。 Hibernate 的插入和查询速度与JDBC相差不大,大概比JDBC慢20%(我这里测试的,不过没用JCS) 我测试SLSB+DAO+CMP和 SLSB+DAO+Hibernate的贴子是 /viewtopic.php?t=18 不过,很早Robbin就说过测试没这么简单,我们这样的做的目的,只是对Hibernte, JDBC, CMP的速度及运作有一个大概的认识,没有什么权威性。测试结果也是仅供参考。 这一点请大家注意。 只要你正确的设置 Batch Size ,一般来说推荐为25,插入更新和删除都不会有性能问题,除非你配置错误。 最容易产生性能问题的地方是,当你使用Hibernate Iterator一次查询大量数据的时候,而你又没有使用JCS,那么性能是非常糟糕的。 如果没有使用JCS,那么当查询大量数据一定要采用List,如果使用JCS,那么用Iterator效率更好,可以看看我在性能版的一个测试报告。使用Hibernate进行大数据量的软件性能测试发布: 2009-3-19 09:54 | 作者

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论