基于hibernate缓存机制的查询性能优化研究

来源:互联网 发布:淘宝优惠券群怎么赚钱 编辑:程序博客网 时间:2024/05/22 03:41

Hibernate是一个面向JAVA环境的对象/关系数据库映射工具,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用面向对象编程思维来操纵关系数据库。对象/关系映射(Object/ Relational Mapping,简称ORM)把对象模型表示的对象映射到基于SQL的关系模型结构中去,使得Hibernate可以应用在任何使用JDBC的场合,可以应用到EJB的J2EE架构中,以取代CMP完成数据持久化的重任,也可以整合到J2EE系统中作为持久层框架。
  缓存是数据库数据在内存中的临时容器,它包含了库表数据在内存中的临时拷贝,位于数据库与数据访问层之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高应用的运行性能。
  相对内存操作,数据库调用是一个代价高昂的过程,然而典型企业级应用结构,数据库往往和应用服务器位于不同的物理服务器,这就意味着每次数据库访问都是一次远程调用,数据的打包拆包,网络传输上的延时等消耗都给网络整体性能造成了严重影响。此时,本地内存中的数据缓存的存在价值就显得特别突出。
  对于ORM而言,缓存显得更为重要,它是持久层性能提升的关键。因为相对JDBC的数据存取,ORM的实现往往要借助更加复杂的机制,以实现内部状态的管理、OR关系的映射等。这些额外的开销使得ORM数据访问效率降低,数据缓存是弥补这里产生的性能差距的一个关键策略。特别是对于查询操作相对频繁的系统而言,良好的缓存管理机制以及合理的缓存应用模式往往是性能提升的关键。
  
  1 Hibernate的缓存机制
  
  1.1 持久化层的缓存的范围
  持久层设计中,往往需要考虑几个不同层次中的数据缓存策略。这些层次的划分标准针对不同情况有所差异,一般而言,ORM的数据缓存应包含如下几个层次:
  事物级缓存(Transaction Layer Cache)
  缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的对象形式。
  应用级/进程级缓存(Application/Process Layer Cache)
  缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。对象的松散数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。
  集群级缓存(Cluster Layer Cache)
  在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
  持久层提供以上多种层次的缓存。如果在事务级缓存中没有查到相应的数据,还可以到进程级或集群级缓存内查询,如果还是没有查到,那么只有到数据库中查询。事务级缓存是持久化层的第一级缓存,通常它是必需的;进程级或集群级缓存是持久化层的第二级缓存,通常是可选的。

 

1.2 hibernate缓存机制
  Hibernate提供了两种缓存,第一种是Session的缓存,又称为一级缓存。由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。第一级缓存是必需的,不允许而且事实上也无法卸除。在第一级缓存中,持久化类的每个实例都具有唯一的OID。如图1所示:
  图1 hibernate的二级缓存结构图
  第二种缓存是SessionFactory的缓存,又可以分为两类:内置缓存和外置缓存。SessionFactory的内置缓存和Session的缓存在实现方式上比较相似,前者是SessionFactory对象的一些集合属性包含的数据,后者是指Session的一些集合属性包含的数据。SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的拷贝,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来,SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。
  SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的拷贝,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的第二级缓存。
  
  由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此第二级缓存是进程范围或者集群范围的缓存。二级缓存将由从属于本SessionFactroy的所有Session实例共享,因此有时成为SessionFactory Level Cache。这个缓存中存放的对象的松散数据。第二级对象有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。缓存适配器(Cache Provider)用于把具体的缓存实现软件与Hibernate集成。第二级缓存是可选的,可以在每个类或每个集合的粒度上配置第二级缓存。
  Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存。
  1.4 二级缓存应用策略。
  二级缓存在一个SessionFactory中有效,是优化的重中之重,因此,各类策略也考虑的较多,但是并非所有的情况都适合于使用二级缓存,需要根据具体情况来决定。同时可以针对某一个持久化对象配置其具体的缓存策略。
  适合于使用二级缓存的情况:
  (1)数据不会被第三方修改;
  一般情况下,会被hibernate以外修改的数据最好不要配置二级缓存,以免引起数据不一致问题。但是如果此数据因为性能的原因需要被缓存,同时又有可能被第三方比如SQL修改,也可以为其配置二级缓存。只是需要在sql执行修改后手动清除cache,以保证数据的一致性。
  (2)数据大小在可接收范围之内;
   如果数据表数据量特别巨大,不适合配置二级缓存。原因是缓存的数据量过大可能会引起内存资源紧张,反而降低性能。
  如果数据表数据量特别巨大,但是经常使用的只是较新部分的数据,也可以为其配置二级缓存。但是必须单独配置其持久化类的缓存策略,比如最大缓存数、缓存过期时间等,将这些参数降低至一个合理的范围(太高会引起内存资源紧张,太低了缓存的意义不大)。
  (3)数据更新频率低;
   对于数据更新频率过高的数据,频繁同步缓存中数据的代价可能和从查询缓存中的数据获得的好处相当,坏处益处相抵消。此时缓存的意义也不大。
  (4)非关键数据(不是财务数据等)
   财务数据等是非常重要的数据,绝对不允许出现或使用无效的数据,所以此时为了安全起见最好不要使用二级缓存。
  
  2 基于缓存的优化策略
  
  =查询性能往往是一个系统性能表现的重要方面。相对数据库的更新、删除操作而言,查询机制的优劣很大程度决定了系统的整体性能。
  2.1 查询缓存的应用
  对于经常使用的查询语句,如果启用了查询缓存(Query Cache),当第一次执行查询语句时,hibernate会把查询结果存放在第二级缓存中。以后再执行该查询语句时,只需从缓存中获得查询结果,从而提高性能。
  查询缓存策略的一般过程如下:
  (1)Query Cache保存了之前查询执行过的Select SQL以及结果集等信息,组成一个Query Key。Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows)等。
  (2)当再次遇到查询请求的时候,就会根据Query Key 从Query Cache找,如果存在就返回这个结果列表;如果不存在,查询数据库获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。
  (3)但是两次查询之间,数据表发生数据变动的话,Hibernate就会自动清除Query Cache中对应的Query Key。
  为了启用Query Cache,我们需要在hibernate.cfg.xml中进行配置,参考配置如下(只列出核心配置项):

 

 

应用程序中必须在查询执行之前,将Query.Cacheable设置为true,而且每次都应该这样。比如:
  ………
  Query query=session.createQuery(hql).setInteger(0.15);
  query.setCacheable(true);
  ………
  但是,Query Cache只在特定的条件下才会发挥作用,而且要求相当严格:
  (1)完全相同的Select SQL重复执行。
  (2)重复执行期间,Query Key对应的数据表不能有数据变动(比如添、删、改操作)。
  并且,查询缓存在一个交易系统(数据变更频繁,查询条件相同的机率并不大)中可能会起反作用:它会白白耗费大量的系统资源但却很难发挥优势。
  2.2 数据查询方法的选用
  完成同样的查询,Hibernate提供了可供选择的一些方式,但具体使用什么方式,可能对性能和代码都会有影响。
  (1)Session.load/get方法均可以根据指定的实体类和id从数据库读取记录,并返回与之对应的实体对象。
  (a)Session. load
  在执行session.load时,Hibernate首先从当前session的一级缓存中获取id对应的值,在获取不到的情况下,将根据该对象是否配置了二级缓存来做相应的处理,如配置了二级缓存,则从二级缓存中获取id对应的值,如仍然获取不到则还需要根据是否配置了延迟加载来决定如何执行,如未配置延迟加载则从数据库中直接获取,在从数据库获取到数据的情况下,Hibernate会相应的填充一级缓存和二级缓存,如配置了延迟加载则直接返回一个代理类,只有在触发代理类的调用时才进行数据库查询的操作。如果未能发现符合条件的记录, load方法会抛出一个ObjectNotFoundException。
  (b)Session.get
  在执行Session.get时,和Session.load不同的就是get方法则仅仅在内部缓存中进行数据查找,如没有发现对应数据,将越过二级缓存,直接调用SQL完成数据读取。直接从数据库中获取id对应的值。如果未能发现符合条件的记录返回null,而且get方法永远直接返回实体类。
  (2)Query.list / Query.iterate方法均可根据指定条件查询并返回符合条件的实体对象集。
  (c)Query.list
  在执行Query.list时,Hibernate的做法是首先检查是否配置了查询缓存,如配置了则从查询缓存中通过一条sql语句获取所有符合条件的记录,并构造相应的实体对象,然后将其纳入缓存。如获取不到则从数据库中进行获取,从数据库获取到后Hibernate将会相应的填充一级、二级和查询缓存,如获取到的为直接的结果集,则直接返回,如获取到的为一堆id的值,则再根据id获取相应的值(Session.load),最后形成结果集返回。
  (d)Query.iterate
  在执行Query.iterate时,和Query.list的不同的在于从数据库获取的处理上,Query.iterate向数据库发起的是 select id from这样的语句,也就是它是先获取符合查询条件的id,之后在进行iterate.next调用时才再次发起session.load的调用获取实际的数据。iterate方法首先在本地缓存中根据id查找对应的实体对象是否存在(类似Session.load方法),如果缓存中已经存在对应的数据,则直接以此数据对象为查询结果,如果没有找到,再执行相应的Select语句获得对应的库表记录。(iterate方法如果执行了数据库读取操作并构建了完整的数据对象,也会将其查询结果纳入缓存)。
  而且,通过iterate,配合缓存管理API,在海量数据查询中可以很好的解决内存问题,如:
  while(it.hasNext()){
  YouObject object = (YouObject)it.next();
  session.evict(youObject);
  sessionFactory.evice(YouObject.class, youObject.getId());}
  如果用list方法,很可能就出OutofMemory错误了。
  可见,在拥有二级缓存并且查询参数多变的情况下,Query.iterate、Session. load会比Query.list及Session.get更为高效。
  
  3 结束语
  
  在实际开发中,运用好缓存可以让系统性能得到良好的提升。可缓存也有它的局限性,什么情况用什么情况不用,都要结合实际情况来考量。很多时候我们是在效率与安全/准确性上找一个平衡点,无论如何,优化都不是一个纯技术的问题,应该对应用和业务特征有足够的了解,如果违背了相关的缓存机制,可能会得到负面效果,所以必须充分考虑实际情况来灵活运用缓存并发挥它的优势。

 

本文原文:http://qkzz.net/magazine/1009-3044/2007/26/zsjs20072696.pdf

原创粉丝点击