为什么ORM性能比iBATIS好doc.docx

上传人:b****7 文档编号:9023487 上传时间:2023-02-02 格式:DOCX 页数:18 大小:30.63KB
下载 相关 举报
为什么ORM性能比iBATIS好doc.docx_第1页
第1页 / 共18页
为什么ORM性能比iBATIS好doc.docx_第2页
第2页 / 共18页
为什么ORM性能比iBATIS好doc.docx_第3页
第3页 / 共18页
为什么ORM性能比iBATIS好doc.docx_第4页
第4页 / 共18页
为什么ORM性能比iBATIS好doc.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

为什么ORM性能比iBATIS好doc.docx

《为什么ORM性能比iBATIS好doc.docx》由会员分享,可在线阅读,更多相关《为什么ORM性能比iBATIS好doc.docx(18页珍藏版)》请在冰豆网上搜索。

为什么ORM性能比iBATIS好doc.docx

为什么ORM性能比iBATIS好doc

缓存是有很多层次的,有webserver前端缓存,有动态页面静态化,有页面片断缓存,有查询缓存,也有对象缓存。

不同层面的缓存适用于不同的应用场景,作用也各自不同,如果可以,你全部一起用上,他们不矛盾,但这个话题比较大,现在不展开谈。

针对OLTP类型的web应用,只要代码写的质量没有问题,最终的性能瓶颈毫无疑问还是数据库查询。

应用服务器层面可以水平扩展,但是数据库是单点的,很难水平扩展,所以如何有效降低数据库查询频率,减轻数据库压力,是web应用性能问题的根源。

以上所有的缓存方式都可以直接或者间接的降低数据库访问,但缓存是有应用场景的,虽然新闻网站非常适合使用动态页面静态化技术,但是例如电子商务网站就不适合动态页面静态化,而页面缓存和查询缓存可以使用的场景也不多。

但是对象缓存是所有缓存技术当中适用场景最广泛的,任何OLTP应用,即使实时性要求很高,你也可以使用对象缓存,而且好的ORM实现,对象缓存是完全透明的,不需要你的程序代码进行硬编码。

用不用对象缓存,怎么用对象缓存,不是一个调优的技巧问题,而是整个应用的架构问题。

在你开发一个应用之前,你就要想清楚,这个应用最终的场景是什么?

会有多大的用户量和数据量。

你将采用什么方式来架构这个应用:

OK,也许你偏爱SQL,那么你选择iBATIS,数据库设计当中大表有很多冗余字段,会尽量消除大表之间的关联关系,最终用户量和访问量很高以后,你会选择使用Oracle,雇佣资深的DBA,进行数据库调优和SQL调优,这是大多数公司走的路。

但是我告诉你,你还有另外一条路可以走。

你可以选择ORM(不见得一定是Hibernate),数据库设计当中避免出现大表,比较多的表关联关系,通过ORM以对象化方式操作。

当用户量和访问量很高以后,除了数据库端本身的优化,你还有对象缓存这条途径。

对象缓存是怎样提高性能的呢?

随便举个例子:

论坛的列表页面,需要显示topic的分页列表,topic作者的名字,topic最后回复帖子的作者,如果是iBATIS,你准备怎么做?

Sql代码

1.select ... from topic left join user left join post .....  

select...fromtopicleftjoinuserleftjoinpost.....

你需要通过joinuser表来取得topic作者的名字,然后你还需要joinpost表取得最后回复的帖子,post再joinuser表取得最后回贴作者名字。

也许你说,我可以设计表冗余,在topic里面增加username,在post里面增加username,所以通过大表冗余字段,消除了复杂的表关联:

Sql代码

1.select ... from topic left join post...  

select...fromtopicleftjoinpost...

OK,且不说冗余字段的维护问题,现在仍然是两张大表的关联查询。

然后让我们看看ORM怎么做?

Sql代码

1.select * from topic where ... --分页条件  select*fromtopicwhere...--分页条件

就这么一条SQL搞定,比上面的关联查询对数据库的压力小多了。

也许你说,不对阿,作者信息呢?

回贴作者信息呢?

这些难道不会发送SQL吗?

如果发送SQL,这不就是臭名昭著的n+1条问题吗?

你说的对,最坏情况下,会有很多条SQL:

Sql代码

1.select * from user where id = topic_id...;  

2.....  

3.select * from user where id = topic_id...;  

4.  

5.select * from post where id = last_topic_id...;  

6.....  

7.select * from post where id = last_topic_id...;  

8.  

9.select * from user where id = post_id...;  

10.....  

11.select * from user where id = post_id...;  

select*fromuserwhereid=topic_id...;

....

select*fromuserwhereid=topic_id...;

select*frompostwhereid=last_topic_id...;

....

select*frompostwhereid=last_topic_id...;

select*fromuserwhereid=post_id...;

....

select*fromuserwhereid=post_id...;

事实上何止n+1,根本就是3n+1条SQL了。

那你怎么还说ORM性能高呢?

因为对象缓存在起作用,你可以观察到后面的3n条SQL语句全部都是基于主键的单表查询,这3n条语句在理想状况下(比较繁忙的web网站),全部都可以命中缓存。

所以事实上只有一条SQL,就是:

Sql代码

1.select * from topic where ...--分页条件  

select*fromtopicwhere...--分页条件

这条单表的条件查询和iBATIS通过字段冗余简化过后的大表关联查询相比,当数据量大到一定程度以后(十几万条),查询的速度会差至少一个数量级,而且对数据库的压力很小,这就是对象缓存的真正威力!

更进一步分析,使用ORM,我们不考虑缓存的情况,那么就是3n+1条SQL。

但是这3n+1条SQL的执行速度一定比iBATIS的大表关联查询慢吗?

不一定!

因为使用ORM的情况下,第一条SQL是单表的条件查询,在有索引的情况下,速度很快,后面的3n条SQL都是单表的主键查询,在繁忙的数据库系统当中,3n条SQL几乎可以全部命中数据库的databuffer。

但是使用iBATIS的大表关联查询,很可能会造成全表扫描,这样性能是非常差的。

所以结论就是:

即使不使用对象缓存,ORM的n+1条SQL性能仍然很有可能超过iBATIS的大表关联查询,而且对数据库造成的压力要小很多。

这个结论貌似令人难以置信,但经过我的实践证明,就是事实。

前提是数据量和访问量都要比较大,否则看不出来这种效果

还是拿上面这个例子的应用场景来说,由于JavaEye网站用RoR的ActiveRecord,所以这个场景事实上就会发送3n+1条SQL语句。

我从log里面看到这密密麻麻的SQL,着实非常担忧性能,所以尝试使用了find的:

include选项去eagerfetch,迫使ActiveRecord发送单条复杂的关联查询。

但非常不幸的是,在网站服务器的production.log里面经过前后对比,发现使用:

include以后,单条复杂关联查询耗时更多,数据库压力更大。

在使用memcached之后,比3n+1条的性能进一步明显提升。

所以性能对比就是这样的:

ORM+Cache>ORMn+1>iBATIS关联查询

那为什么应用Cache可以进一步提高性能,是因为访问Cache的开销比访问数据库小的得多造成的。

应用程序根据主键key去CacheServer取value,是非常简单的算法,开销极小。

而发送一条主键查询的SQL到数据库,要经过非常复杂的过程,有SQL的解析,执行计划的优化,占位符参数的代入,只读事务的保护和隔离等等,最终虽然也命中了数据库的databuffer,但是开销确实很大。

BerkeleyDB就是一个极好的证明,它号称其查询速度是Oracle的1000倍,不是因为它做的比Oracle牛,而是因为它本质上就是一个大Cache,查询没有额外的开销。

///////////////////////////////////////////////////////////////////////////////////////

ORM缓存策略

ORM的数据缓存策略分为三个层次:

1.事务级缓存(TransactionLayerCache)

在当前事务范围内的数据缓存策略,

这个事务可能是一个数据库事务,也可能是某个应用级事务.对于hibernate而言,事务级缓存是基于session的生命周期而存在的,每个session实例都会维护一个数据缓存,此缓存随着session的创建(销毁)而存在(消亡).因此也称为SessionLevelCache.

2.应用级/进程级缓存(Application/ProcessLayerCache)

在某个应用中,或者应用中某个数据访问子集中的共享缓存.

此缓存可由多个事务(数据库事务或应用级事务)共享.对于hibernate而言,应用级缓存在SessionFactory层实现,由这个SessionFactory创建的session实例共享此缓存.因此也称为SessionFactoryLevelCache.

但在多实例并发运行的环境中不能使用应用级缓存.

3.分布式缓存(ClusterLayerCache)

在多个应用实例,多个JVM之间共享的缓存模式.

由多个应用级缓存实例组成集群,通过某种远程机制(如RMI或JMS)实现各个缓存实例间的数据同步,任何一个实例的数据修改操作,将导致整个集群间的数据状态同步.

////////////////////////////////////////////////////////////////////////////////////

最近做一个比较大的电子商务项目,预计每天订单量将在5万多单,客服人员需要频繁的下单、查询订单、操作订单,客人预订完订单后,会立即进入处理流程,为了提高服务质量,要求流水化作业,平均要在40分钟-80分钟内处理完订单,对于疑难订单要到第二天,才能处理完。

所以订单在创建后,会在短时间内,被频繁的修改和查看。

  由于在项目中ORM层主要是基于Hibernate框架,所以在调优时,很自然的就想到打开Hibernate的二级缓存,以次来减小由于Load 订单大对象时N+1次查询给数据造成的压力,自己做的测试效果也非常好,也顺利通过压力测试。

  但在上线时,性能却并不佳,经过分析业务的操作特点,查找原因有以下几占:

   1.但由于中台每天在工作当中,频繁的批量分配工单,

   

   因为要批量将订单分配给某一个工作人员处理,在代码当中执行了一个bulkUpdate的操作:

  template.bulkUpdate("updateordersetowner=?

whereidin(?

?

?

)");

   

   这时Hibernate会直接将Order对象的二级缓存清楚掉。

   由于二级缓存,总是被刷掉,再查询时,需要重新从数据库Load,所以二级缓存变相直接起的作用很少。

   2.由于工作人员在处理订单时,每一次查看之后,都有更新操作,在更新之后,订单被清除缓存,下一组人在处理订单时,又得重新LOAD,所以效果并不好。

   3.无论是白盒测试,还是压力测试时,所基于的案例太过于简单,没有更深入的模仿业务操作,对于压力测试的脚本,也很难精确的模拟出真实的流程化的业务操作过程。

   开始想到,直接获得Session,直接使用JDBC来编写更新代码,并在更新后,使用sessionFactory.evict(Order.class,id);来有目标的逐个清除特定的对象,以避免全部清楚缓存。

   但样做,会对DAO层,修改过大。

   由于整个模块最核心的商业对象就是订单,最后决定在Service层对订单开小灶,对订单缓存的单独的定制处理。

   我觉得应用缓存存在以下优点:

     1。

速度要快于ORM缓存,

     2。

对于缓存的控制权更大,可以直接控制缓存工具的API进行操作,可以避免一些盲目清除的操作。

     3。

更灵活的控制缓存中对象的失效,如根据事件来清除缓存,如订单的处理流程结束时,将该订单从缓存中清除掉,

     4。

在更新数据库时,不是直接清除缓存,而是更新缓存(尽管这有风险),当业务层抛出异常时,才去清空缓存,避免由于频繁更新,而清空缓存。

   缺点:

    1。

订单的更新操作,必须是单点的,只能通过IOrderService提供的接口,进行更新操作,否则数据不一致的风险较大。

    2。

想要透明化,需要有一定的代码工作量,不容易达到ORM缓存最强大的那种透明化和灵活可配置,你可以使用Ehcache,也可以选Jboss,有钱的话,可以用Tangosol。

    3。

如果不对第三方缓存包,进行一定的封装的话,会直接耦合于第三方的缓存包,不能像Hibernate那样,灵活选择和配置缓存工具。

    4。

对业务层代码有一定的侵

   目前的方案是采用应用层的现代化,同时使用如Proxy模式来提供透明化的设计,

     IOrderService-》 OrderService-》CacheableOrderService

   通过Spring的Bean配置,一样可以实现透明化的操作。

   结论:

   1。

缓存的清空与更新,要尽量精确的去操作受到更新影响的对象,而不是全部搞掉。

     在Hibernate当中,也提供了sessionFactory.evict(class,id)这样细粒度的清空缓存对象的方法。

         sessionFactory.evice(class)的操作,要看这样的操作是否频繁,如果频繁,对于缓存的作用就会大大的折扣。

   2。

如果缓存对象过多,对于失效的算法与处理,要与业务对象的特性紧密的联合起来,通过事件来驱动对象的失效。

   3。

对于商业对象的缓存,必须要深刻分析对象的生命周期,业务特性。

   4。

对于数据不一致的风险,要有足够的认识与预防手段。

   5。

合理的估计订单对象的大小,分配足够的内存

   6。

如果只使用中心缓存,只能减小数据库的压力,对于网络带宽的压力,还是有的,速度上也远远逊于本地缓存的效果,所以要结合本地缓存+中心缓存的策略方案,即提高速度,避免群集复制时的瓶颈。

//////////////////////////////////////////////////////////////////////////////////////

相关文章:

 

∙Hibernate缓存机制

∙细谈Ehcache页面缓存的使用

∙怎样判断对象是否存在于缓存中

推荐圈子:

Hibernate

更多相关推荐

很多人对二级缓存都不太了解,或者是有错误的认识,我一直想写一篇文章介绍一下hibernate的二级缓存的,今天终于忍不住了。

我的经验主要来自hibernate2.1版本,基本原理和3.0、3.1是一样的,请原谅我的顽固不化。

hibernate的session提供了一级缓存,每个session,对同一个id进行两次load,不会发送两条sql给数据库,但是session关闭的时候,一级缓存就失效了。

二级缓存是SessionFactory级别的全局缓存,它底下可以使用不同的缓存类库,比如ehcache、oscache等,需要设置hibernate.cache.provider_class,我们这里用ehcache,在2.1中就是

hibernate.cache.provider_class=net.sf.hibernate.cache.EhCacheProvider

如果使用查询缓存,加上

hibernate.cache.use_query_cache=true

缓存可以简单的看成一个Map,通过key在缓存里面找value。

Class的缓存

对于一条记录,也就是一个PO来说,是根据ID来找的,缓存的key就是ID,value是POJO。

无论list,load还是iterate,只要读出一个对象,都会填充缓存。

但是list不会使用缓存,而iterate会先取数据库selectid出来,然后一个id一个id的load,如果在缓存里面有,就从缓存取,没有的话就去数据库load。

假设是读写缓存,需要设置:

<cacheusage="read-write"/>

如果你使用的二级缓存实现是ehcache的话,需要配置ehcache.xml

<cachename="com.xxx.pojo.Foo"maxElementsInMemory="500"eternal="false"timeToLiveSeconds="7200"timeToIdleSeconds="3600"overflowToDisk="true"/>

其中eternal表示缓存是不是永远不超时,timeToLiveSeconds是缓存中每个元素(这里也就是一个POJO)的超时时间,如果eternal="false",超过指定的时间,这个元素就被移走了。

timeToIdleSeconds是发呆时间,是可选的。

当往缓存里面put的元素超过500个时,如果overflowToDisk="true",就会把缓存中的部分数据保存在硬盘上的临时文件里面。

每个需要缓存的class都要这样配置。

如果你没有配置,hibernate会在启动的时候警告你,然后使用defaultCache的配置,这样多个class会共享一个配置。

当某个ID通过hibernate修改时,hibernate会知道,于是移除缓存。

这样大家可能会想,同样的查询条件,第一次先list,第二次再iterate,就可以使用到缓存了。

实际上这是很难的,因为你无法判断什么时候是第一次,而且每次查询的条件通常是不一样的,假如数据库里面有100条记录,id从1到100,第一次list的时候出了前50个id,第二次iterate的时候却查询到30至70号id,那么30-50是从缓存里面取的,51到70是从数据库取的,共发送1+20条sql。

所以我一直认为iterate没有什么用,总是会有1+N的问题。

(题外话:

有说法说大型查询用list会把整个结果集装入内存,很慢,而iterate只selectid比较好,但是大型查询总是要分页查的,谁也不会真的把整个结果集装进来,假如一页20条的话,iterate共需要执行21条语句,list虽然选择若干字段,比iterate第一条selectid语句慢一些,但只有一条语句,不装入整个结果集hibernate还会根据数据库方言做优化,比如使用mysql的limit,整体看来应该还是list快。

如果想要对list或者iterate查询的结果缓存,就要用到查询缓存了

查询缓存

首先需要配置hibernate.cache.use_query_cache=true

如果用ehcache,配置ehcache.xml,注意hibernate3.0以后不是net.sf的包名了

<cachename="net.sf.hibernate.cache.StandardQueryCache"

  maxElementsInMemory="50"eternal="false"timeToIdleSeconds="3600"

  timeToLiveSeconds="7200"overflowToDisk="true"/>

<cachename="net.sf.hibernate.cache.UpdateTimestampsCache"

  maxElementsInMemory="5000"eternal="true"overflowToDisk="true"/>

然后

query.setCacheable(true);//激活查询缓存

query.setCacheRegion("myCacheRegion");//指定要使用的cacheRegion,可选

第二行指定要使用的cacheRegion是myCacheRegion,即你可以给每个查询缓存做一个单独的配置,使用setCacheRegion来做这个指定,需要在ehcache.xml里面配置它:

<cachename="myCacheRegion"maxElementsInMemory="10"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="7200"overflowToDisk="true"/>

如果省略第二行,不设置cacheRegion的话,那么会使用上面提到的标准查询缓存的配置,也就是net.sf.hibernate.cache.StandardQueryCache

对于查询缓存来说,缓存的key是根据hql生成的sql,再加上参数,分页等信息(可以通过日志输出看到,不过它的输出不是很可读,最好改一下它的代码)。

比如hql:

fromCatcwherec.namelike?

生成大致如下的sql:

select*fromcatcwherec.namelike?

参数是"tiger%",那么查询缓存的key*大约*是这样的字符串(我是凭记忆写的,并不精确,不过看了也该明白了):

select*fromcatcwherec.namelike?

parameter:

tiger%

这样,保证了同样的查询、同样的参数等条件下具有一样的key。

现在说说缓存的value,如果是list方式的话,value在这里并不是整个结果集,而是查询出来的这一串ID。

也就是说,不管是list方法还是iterate方法,第一次查询的时候,它们的查询方式很它们平时的方式是一样的,list执行一条sql,iterate执行1+N条,多出来的行为是它们填充了缓存。

但是到同样条件第二次查询的时候,就都和iterate的行为一样了,根据缓存的key去缓存里面查到了value,value是一串id,然后在到class的缓存里面去一个一个的load出来。

这样做是为了节约内存。

可以看出来,查询缓存需要打开相关类的class缓存。

list和iterate方法第一次执行的时候,都是既填充查询缓存又填充class缓存的。

这里还有一个很容易被忽视的重要问题,即打开查询缓存以后,即使是list方法也可能遇到1+N的问题!

相同条件第一次list的时候,因为查询缓存中找不到,不管class缓存是否存在数据,总是发送一条sql语句到数据库获取全部数据,然后填充查询缓存和class缓存。

但是第二次执行的时候,问题就来了,如果你的class缓存的超时时间比较短,现在class缓存都超时了,但是查询缓存还在,那么list方法在获取id串以后,将会一个一个去数据库load!

因此,class缓存的超时时间一定不能短于查询缓存设置的超时时间!

如果还设置了发

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1