`
benx
  • 浏览: 272812 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Hibernate 二级缓存 和 查询缓存 (转)

    博客分类:
  • java
阅读更多

Hibernate 二级缓存 查询缓存

 

二级缓存和查询缓存都相当于一个map

二级缓存缓存的keyidvalue为实体对象。一般loadget ,iterate使用到二级缓存,list()需要结合查询缓存使用。

iteratelist区别如下:

iterate不需要开启查询缓存,它首先发出一个sql如”select s.id from Student s”去数据库把id属性列表取出来,然后再根据id列表一个一个load(),如果缓存有从缓存取,如果缓存没有就从数据库取:select s.id,s.name,s.classid from     Student s where s.id=?,取出后再存入二级缓存。Iterate总会发出取id列表的语句。

 

List需要开启查询缓存,它首先发出一个sql如”select s.id,s.name,s.classid from Student s…”去数据库取出所有相关实体,并将这些实体存入二级缓存,将此sql语句及一些相关信息作为keyid列表作为值,第二次查询这条语句时就会去根据sql语句及相关信息去key里找,如果有就会把id列表取出一个一个load(),接下来就和iterate一样了。List一般只有第一次发发出取实体列表的语句,以后的id列表就会去查询缓存取id列表,不会再发出sql语句。

 

前提:执行同一hql语句,如:select s from Student s

1.关闭查询缓存,开启二级缓存时:

第二次查询属性时iterate只会发出获取id列表的sqllist会发出和第一次一样的请求实体的sql

2.开启查询缓存,开启二级缓存

第二次查询属性时iterate只会发出获取id列表的sqllist不发sql

以上说明iterate只和二级缓存有关,list和二级缓存和查询缓存都有关。

 

 

list 不会使用二级缓存,但是会填充二级缓存,还会使用查询缓存

 

                                                    out join          使用查询缓存二级缓存

 

1.Criteria.list                                                       

2.Query.list                                    不会                   

3.Query.iterate                                                   

4.ACriteria.list                            

   BQuery.list                               不会               不会使用A的结果缓存

5.AQuery.list                               不会

   BCriteria.list                                               不会使用A的结果缓存

6.AQuery.iterate                        

   BQuery.list                               不会               不会使用A的结果缓存

7.AQuery.list                               不会

   BQuery.iterate                         不会                 使用A的结果缓存

8.ACriteria.list                            

   BQuery.iterate                                                使用A的结果缓存

9.AQuery.iterate                        

   BCriteria.list                                              不会使用A的结果缓存

 

 

1.二级缓存

如果开启了二级缓存,hibernate在执行任何一次查询的之后,都会把得到的结果集放到缓存中,缓存结构可以看作是一个hash tablekey是数据库记录的idvalueid对应的pojo对象。当用户根据id查询对象的时候(loaditerator方法),会首先在缓存中查找,如果没有找到再发起数据库查询。但是如果使用hql发起查询(find, query方法)则不会利用二级缓存(如果开启查询缓存,会使用查询缓存,否则会查询数据库),而是直接从数据库获得数据,但是它会把得到的数据放到二级缓存备用。也就是说,基于hql的查询,对二级缓存是只写不读的。

 

针对二级缓存的工作原理,采用iterator取代list来提高二级缓存命中率的想法是不可行的。Iterator的工作方式是根据检索条件从数据库中选取所有目标数据的id,然后用这些id一个一个的到二级缓存里面做检索,如果找到就直接加载,找不到就向数据库做查询。因此假如iterator检索100条数据的话,最好情况是100%全部命中,最坏情况是0%命中,执行101sql把所有数据选出来。而list虽然不利用缓存,但是它只会发起1sql取得所有数据。在合理利用分页查询的情况下,list整体效率高于iterator

二级缓存的失效机制由hibernate控制,当某条数据被修改之后,hibernate会根据它的id去做缓存失效操作。基于此机制,如果数据表不是被hibernate独占(比如同时使用JDBC或者ado等),那么二级缓存无法得到有效控制。

 

由于hibernate的缓存接口很灵活,cache provider可以方便的切换,因此支持cluster环境不是大问题,通过使用swarmcachejboss cache等支持分布式的缓存方案,可以实现。但是问题在于:

1、 分布式缓存本身成本偏高(比如使用同步复制模式的jboss cache

2、 分布式环境通常对事务控制有较高要求,而目前的开源缓存方案对事务缓存(transaction cache)支持得不够好。当jta事务发生会滚,缓存的最后更新结果很难预料。这一点会带来很大的部署成本,甚至得不偿失。

结论:不应把hibernate二级缓存作为优化的主要手段,一般情况下建议不要使用

原因如下:

1、 由于hibernate批量操作的性能不如sql,而且为了兼容1.0dao类,所以项目中有保留了sql操作。哪些数据表是单纯被hibernate独占无法统计,而且随着将来业务的发展可能会有很大变数。因此不宜采用二级缓存。

2、 针对系统业务来说,基于id检索的二级缓存命中率极为有限,hql被大量采用,二级缓存对性能的提升很有限。

3 hibernate 3.0在做批量修改、批量更新的时候,是不会同步更新二级缓存的,该问题在hibernate 3.2中是否仍然存在尚不确定。

 

 

二、hibernate的查询缓存

查询缓存的实现机制与二级缓存基本一致,最大的差异在于放入缓存中的key是查询的语句value是查询之后得到的结果集的id列表。表面看来这样的方案似乎能解决hql利用缓存的问题,但是需要注意的是,构成key的是:hql生成的sqlsql的参数、排序、分页信息等。也就是说如果你的hql有小小的差异,比如第一条hql1-50条数据,第二条hql20-60条数据,那么hibernate会认为这是两个完全不同的key,无法重复利用缓存。因此利用率也不高。

总结:也就是查询缓存的hql必须一致,才可以使用

 

另外一个需要注意的问题是,查询缓存和二级缓存是有关联关系的,他们不是完全独立的两套东西。假如一个查询条件hql_1,第一次被执行的时候,它会从数据库取得数据,然后把查询条件作为key,把返回数据的所有id列表作为value(请注意仅仅是id)放到查询缓存中,同时整个结果集放到class缓存(也就是二级缓存),keyidvaluepojo对象。当你再次执行hql_1,它会从缓存中得到id列表,然后根据这些列表一个一个的到class缓存里面去找pojo对象,如果找不到就向数据库发起查询。也就是说,如果二级缓存配置了超时时间(或者发呆时间),就有可能出现查询缓存命中了,获得了id列表,但是class里面相应的pojo已经因为超时(或发呆)被失效,hibernate就会根据id清单,一个一个的去向数据库查询,有多少个id,就执行多少个sql。该情况将导致性能下降严重。

总结:也就是查询缓存如果hql一样,也有可能发生N+1查询

 

查询缓存的失效机制也由hibernate控制,数据进入缓存时会有一个timestamp,它和数据表的timestamp对应。当hibernate环境内发生saveupdate等操作时,会更新被操作数据表的timestamp。用户在获取缓存的时候,一旦命中就会检查它的timestamp是否和数据表的timestamp匹配,如果不,缓存会被失效。因此查询缓存的失效控制是以数据表为粒度的,只要数据表中任何一条记录发生一点修改,整个表相关的所有查询缓存就都无效了。因此查询缓存的命中率可能会很低。

总结:查询缓存会因为表修改而失效

 

结论:不应把hibernate二级缓存作为优化的主要手段,一般情况下建议不要使用。

原因如下:

1、 项目上层业务中检索条件都比较复杂,尤其是涉及多表操作的地方。很少出现重复执行一个排序、分页、参数一致的查询,因此命中率很难提高。

2、 查询缓存必须配合二级缓存一起使用,否则极易出现1+N的情况,否则性能不升反降

3、 使用查询缓存必须在执行查询之前显示调用Query.setCacheable(true)才能激活缓存,这势必会对已有的hibernate封装类带来问题。

总结

详细分析hibernate的二级缓存和查询缓存之后,在底层使用通用缓存方案的想法基本上是不可取的。比较好的做法是在高层次中(业务逻辑层面),针对具体的业务逻辑状况手动使用数据缓存,不仅可以完全控制缓存的生命周期,还可以针对业务具体调整缓存方案提交命中率。Cluster中的缓存同步可以完全交给缓存本身的同步机制来完成。比如开源缓存swarmcache采用invalidate的机制,可以根据用户指定的策略,在需要的时候向网络中的其他swarmcache节点发送失效消息,建议采用。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics