业务系统中使用Hibernate链接数据库的很多,在熟悉Hibernate的基础上做开发,可以大大缩短项目周期,提高开发效率,不用为类型转换、有效性判断、对象映射等等写重复代码。但是Hibernate本身的对象结构,以及设计思路,跟传统的直接SQL访问数据库还是有一些不同的。下面总结几个Hibernate的开发,在性能方面应该注意的几个点。
1、many-to-one级联对象的fetch是用select还是用join?
假如对象关系是A->B,如果用select的话,通过A对象获取B对象,Hibernate一定会再生成一条SQL语句:select
* from B b where
b.id=?。用惯SQL的同学一定极为不爽,既然A->B是必然使用的,为什么不在获取A的时候,一条SQL语句把B选出来呢,于是会把fetch善意的改成join。接着灾难开始了。系统里,除了A->B的关系,可能还存在C->A->B、D->A->B、E->C->A->B,如果手贱的把E->C->A->B的fetch属性都改成join,那你在获取E的时候,同时会join到C、A、B。E对象只通过主键获取一个对象倒还罢了,如果是一个几百万的列表呢?
在映射文件中使用join,是一种偷懒行为。如果真的需要直接join出B对象,可以在hql里使用“join
fetch”解决。首先,通过主键索引select出来的对象,往往比其他索引快一个数量级;其次,用hibernate一定要用其缓存机制,这里说的是其二级缓存机制。系统中如果再使用分页机制的话,几百万条A数据,要得到B数据,也就做几十个(取决于你每页数据量)主键索引的select,而且再次使用相应B对象的时候,会直接从缓存中拿。
2、一张大表装所有数据,还是多个小表级联呢?
单表单查询当然能获得最好的查询性能,所以很多做互联网的喜欢搞大表,逆范式,然后在表上堆索引,优化查询的时候使用强制索引。于是乎,用Hibernate的时候,也这么干。但是,Hibernate查询是基于对象的哦,每次select的时候,不管这个属性要不要都拿出来的,数据传输、转换都是要消耗系统资源的。另外,Hibernate不提供强制索引的能力,除非你自己写方言。搞大表是种惯性思维,Hibernate是在范式的思维下设计的。解决查询性能问题,需要综合考虑。如果已经做了大表,可以使用discriminator做一下对象继承关系,可以有效避免部分冗余数据的查询。
3、一条HQL,还是多条HQL
做数据库查询,往往喜欢用一条语句得到数据列表,多表级联就用join
fetch。SQL经验会告诉我们,1条搞定全部查询,是效率最高的,所以很多人比拼如何把语句凑成一条。首先一条语句解决一个查询的执行效率最高是否合理,可以参看我的另外一篇blog 《一条Mysql中的子查询in
(select)语句引发的血案》 ;另外,对于Hibernate来说,在有级联对象的时候,一条语句中的join
fetch,是用不到缓存的,而对于one-to-many的集合对象,查询效率更低。
一条select会承载太多的东西:where、distinct、order
by、group
by、limit,如果再加上join语句,性能肯定高不了。应该尽量避免无用的distinct;默认列表的查询条件应该都在主表;能不用order
by就不用;一对多关系的对象、group
by的数据可以考虑采用另外的hql语句;MySQL的limit没办法,几十万条以后,尾部的数据就是会慢。
总结下。数据对象尽量设计好,至少达到第二范式。Hibernate的查询,应该尽最大可能的利用好缓存,包括对象缓存、集合缓存和查询缓存。试试把你分页时的select
count(*) from A
where的查询放到缓存里,就可以提高一倍的查询效率了。没有二级缓存的Hibernate应用,是达不到应用级别的。