缓存是有很多层次的,有web server前端缓存,有动态页面静态化,有页面片断缓存,有查询缓存,也有对象缓存。不同层面的缓存适用于不同的应用场景,作用也各自不同,如果可以,你全部一起用上,他们不矛盾,但这个话题比较大,现在不展开谈。

针对OLTP类型的web应用,只要代码写的质量没有问题,最终的性能瓶颈毫无疑问还是数据库查询。应用服务器层面可以水平扩展,但是数据库是单点的,很难水平扩展,所以如何有效降低数据库查询频率,减轻数据库压力,是web应用性能问题的根源。

以上所有的缓存方式都可以直接或者间接的降低数据库访问,但缓存是有应用场景的,虽然新闻网站非常适合使用动态页面静态化技术,但是例如电子商务网站就不适合动态页面静态化,而页面缓存和查询缓存可以使用的场景也不多。但是对象缓存是所有缓存技术当中适用场景最广泛的,任何OLTP应用,即使实时性要求很高,你也可以使用对象缓存,而且好的ORM实现,对象缓存是完全透明的,不需要你的程序代码进行硬编码。

用不用对象缓存,怎么用对象缓存,不是一个调优的技巧问题,而是整个应用的架构问题。在你开发一个应用之前,你就要想清楚,这个应用最终的场景是什么?会有多大的用户量和数据量。你将采用什么方式来架构这个应用:

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

但是我告诉你,你还有另外一条路可以走。你可以选择ORM(不见得一定是Hibernate),数据库设计当中避免出现大表,比较多的表关联关系,通过ORM以对象化方式操作。当用户量和访问量很高以后,除了数据库端本身的优化,你还有对象缓存这条途径。对象缓存是怎样提高性能的呢?随便举个例子:

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

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


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

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

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


OK,且不说冗余字段的维护问题,现在仍然是两张大表的关联查询。然后让我们看看ORM怎么做?

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


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

也许你说,不对阿,作者信息呢?回贴作者信息呢?这些难道不会发送SQL吗?如果发送SQL,这不就是臭名昭著的n+1条问题吗?

你说的对,最坏情况下,会有很多条SQL:
select * from user where id = topic_id...;
....
select * from user where id = topic_id...;

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

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


事实上何止n+1,根本就是3n+1条SQL了。那你怎么还说ORM性能高呢?

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

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


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

更进一步分析,使用ORM,我们不考虑缓存的情况,那么就是3n+1条SQL。但是这3n+1条SQL的执行速度一定比iBATIS的大表关联查询慢吗?不一定!因为使用ORM的情况下,第一条SQL是单表的条件查询,在有索引的情况下,速度很快,后面的3n条SQL都是单表的主键查询,在繁忙的数据库系统当中,3n条SQL几乎可以全部命中数据库的data buffer。但是使用iBATIS的大表关联查询,很可能会造成全表扫描,这样性能是非常差的。

所以结论就是:即使不使用对象缓存,ORM的n+1条SQL性能仍然很有可能超过iBATIS的大表关联查询,而且对数据库造成的压力要小很多。这个结论貌似令人难以置信,但经过我的实践证明,就是事实。前提是数据量和访问量都要比较大,否则看不出来这种效果

还是拿上面这个例子的应用场景来说,由于JavaEye网站用RoR的ActiveRecord,所以这个场景事实上就会发送3n+1条SQL语句。我从log里面看到这密密麻麻的SQL,着实非常担忧性能,所以尝试使用了find的:include选项去eager fetch,迫使ActiveRecord发送单条复杂的关联查询。但非常不幸的是,在网站服务器的production.log里面经过前后对比,发现使用:include以后,单条复杂关联查询耗时更多,数据库压力更大。

在使用memcached之后,比3n+1条的性能进一步明显提升。所以性能对比就是这样的:

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

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

应用程序根据主键key去Cache Server取value,是非常简单的算法,开销极小。
而发送一条主键查询的SQL到数据库,要经过非常复杂的过程,有SQL的解析,执行计划的优化,占位符参数的代入,只读事务的保护和隔离等等,最终虽然也命中了数据库的data buffer,但是开销确实很大。

BerkeleyDB就是一个极好的证明,它号称其查询速度是Oracle的1000倍,不是因为它做的比Oracle牛,而是因为它本质上就是一个大Cache,查询没有额外的开销。
评论
supttkl 2008-04-12
受益匪浅,以前一直鄙视hibernate
bei79 2008-02-21
同意wtusmchen的观点,同时感谢Robbin提出的这个议题, 这种话题向来是越辨越明的.

还有就是想弱弱的问下:
javaeye中是怎么做到每个用户有自已的访问域名的, 类似:
http://robbin.javaeye.com
知道的不要笑话俺, 俺就是很感兴趣, 想知道一下. 先行谢过.
dimscar 2007-09-04
我是从IBATIS到HIBERNATE的,我用到今天,二个各有各自的好处,IBATIS的CACHE也一样好用,也一样的简单,HIBERNATE也一样,至于二个比较嘛,不能说哪个比哪个好,只能说各自应用喽,淘宝还用IBATIS呢!
javafanwind 2007-08-22
ltian 2007-07-02 16:01
弱弱地问问.什么是OLTP类型的web应用?
OLTP
在线事务处理。。。
ltian 2007-07-02
弱弱地问问.什么是OLTP类型的web应用?
element1999 2007-06-28
从文中举的例子来说明orm的效率高于ibatis,很难让人信服。
首先需要肯定的是在高负荷,cache中命中率较高的情况下,orm效率高于ibatis并不奇怪。
但是,如果orm没有缓存。依靠sql语句进行数据访问。那么效率的高低主要取决你sql调优的情况。并不是简单的left join而已。如果真的想用ibatis,需要较高的效率,那么我相信,找一个DBA调优数据库,写sql语句。没有缓存的orm和ibatis还是无法比拟的。
systembug 2007-06-28
ibatis也是ORM,只不过实现的方式是Sql Map。不知是使用什么ORM和ibatis进行比较。而且这个本就不具有可比性。
drinkjava 2007-06-26
换个例子吧,至少这个例子不合适,如果不用ORM,最高效的做法是在topic表中加入作者,最后回贴人,最后回贴时间,回贴总数等冗余数据,把复杂性转移到业务逻辑代码中,这样一个SQL就解决了: select * from topic,并不是如作者所说一定要作left join查询。
X.D.Hua 2007-06-26
我觉得这篇文章无疑对我帮助很大,看来技术留给我还有很大的思考空间,很感谢robbin,希望以后还能看到这样的文章。
lizhanwei 2007-06-13
lszone 2007-06-12
robin说的是缓存策略以及数据库设计的问题,觉得跟ORM和ibatis似乎没有关系
lszone 2007-06-12
不苟同,觉得不是ORM和sqlMap的问题,我们现在用的ibatis,同样用的pojo的cache,而且对于sql的控制更加灵活
zhkchi 2007-06-06
只知道ibatis的人说,他们不认为ibatis就比hibernate要慢
yefeng 2007-06-04
偶是个菜鸟,也想说一下自己的意见,我个人觉得IBATIS和HIBERNATE的学习成本,就不是一个数量级的,而且HIBERNATE基本上没什么好方法来支持分库,或者多个库之间来后的切.
ideage 2007-06-02
看晕了。
现在既不用ORM,也不用iBATIS了。
shiwentao1982 2007-06-01
没有好与不好吧,只有适合与不适合
box 2007-05-28
这题目的确哗众取宠,
比较的是什么都不知道
zlst 2007-05-28
robbin长胖了哈
wtusmchen 2007-05-27
呵呵,先检讨一下我的小人作风,以前见多了robbin尖酸刻薄的语气,好不容易发现他的一点错误,还不抓住机会“蹂躏”一下他。sorry一下先。
回到这个问题上来,如果对一个产品了解不深就随意的作比较,而且语气是如此肯定--“为什么ORM性能比iBATIS好?”,很令人反感!!
说说我的理解,不对的地方多多指正:
1.先撇开缓存不说
所谓的ORM/IBATIS之类的产品也就是一个搬运工,用户告诉它去取A表的数据,它就老老实实组合sql去数据库搬运,这个性能完全取决于数据库。对关联表的查询无非就两种,就像robbin说的那样--join和n+1,这两种做法哪个效率更高姑且不论,ORM和IBATIS都支持,ibatis里面
select a.*,b.* from A,B where A.id=B.AID ---关联查询
select * from A  & select * form B where AID=? --n+1

所以结论就是:即使不使用对象缓存,ORM的n+1条SQL性能仍然很有可能超过iBATIS的大表关联查询,而且对数据库造成的压力要小很多。这个结论貌似令人难以置信,但经过我的实践证明,就是事实。前提是数据量和访问量都要比较大,否则看不出来这种效果
--这个结论就让我很不理解了,应该拿ORM的n+1与IBATIS的n+1作比较,怎么会拿n+1与关联查询作性能比较?
2.缓存
ORM/IBATIS之类的产品还是一个搬运工(呵呵,可能我上辈子是搬运工出身),收到客户请求的时候先到小仓库找一下,如果没有再去大仓库(数据库),这个性能决定于小仓库的检索效率。在ibatis里面的用法:
1.查询主表select * from A
2.根据ID取B表的数据
<cacheModel id="B-cache" type="OSCACHE">
<flushInterval hours="24"/>
</cacheModel>
<statement id="getB" cacheModel="B-cache">
select * from B where AID = ?
</statement>

如果非要比较ORM和IBATIS的话,我认为应该是比较ORM的缓存和ibatis的缓存效率,据我了解,Hibernate和Ibatis都能使用OSCACHE,我想性能应该相差无几(其它缓存我就不知道了,不敢瞎说)
ORM + Cache > ORM n+1 > iBATIS 关联查询
这个结论我认为改成这个比较合适:
ORM + Cache > ORM n+1
ORM + Cache > ORM 关联查询
ORM + Cache > iBATIS n+1
ORM + Cache > iBATIS 关联查询
IBATIS + Cache > ORM n+1
IBATIS + Cache > ORM 关联查询
IBATIS + Cache > iBATIS n+1
IBATIS + Cache > iBATIS 关联查询
至于ORM + CacheIBATIS + Cache的性能比较如何只能有待们去实践中检验!
grandboy 2007-05-26
wtusmchen,有些话有点刻薄了, 即使是大师也有不会的东西啦!
你所说的对关联表做缓存,不太懂.你指的是自己做缓存,还是利用IBatis缓存机制? 不知道IBatis的缓存机制做得怎么样? 有没有这方面的数据?
发表评论

您还没有登录,请登录后发表评论

robbin
搜索本博客
我的相册
213cbb75-7dae-37b2-b9ce-9e7b49f784d3-thumb
游乌镇
共 33 张
其他分类
存档
最新评论