`
AutomaticThoughts
  • 浏览: 162221 次
社区版块
存档分类
最新评论

Hibernate性能优化

阅读更多

有很多人认为Hibernate天生效率比较低,确实,在普遍情况下,需要将执行转换为SQL语句的 Hibernate的效率低于直接JDBC存取,然而,在经过比较好的性能优化之后,Hibernate的性能还是让人相当满意的,特别是应用二级缓存之 后,甚至可以获得比较不使用缓存的JDBC更好的性能,下面介绍一些通常的Hibernate的优化策略:
1.抓取 优化
抓取是指Hibernate如何在关联关系之间进行导航的时候,Hibernate如何获取关联对象的策略,其主要定义了两个方面:如何抓取和何时抓取
1)如何抓取。
Hibernate3主要有两种种抓取方式,分别应用于对象关联实例(many-to-one、one-to-one)和对象关联集合(set、map等),总共是四种变种
JOIN抓取: 通过在SELECT语句中使用OUTER JOIN来获得对象的关联实例或者关联集合)
SELECT抓取: 另外发送一条SELECT语句来抓取当前对象的关联实体和集合
在我的开发经历中,此处对性能的优化是比较有限的,并不值得过多关注
例:
A.应用于对象关联实例(默认是false)
<many-to-one name=".." outer-join="true/false/auto" .../>
B.应用于对象关联集合(默认是auto)
<set name=".." fetch="join/select" ... >
....
</set>
2)何时抓取
主要分为延迟加载和立即抓取,默认的情况下Hibernate3对对象关联实采用延迟加载,普通属性采用立即抓取,通过延迟加载和采用适当的抓取粒度,与不采用优化相比往往可以将性能提升数倍
立即抓取:当抓取宿主对象时,同时抓取其关联对象和关联集以及属性
延迟加载:当抓取宿主对象时,并不抓取其关联对象,而是当对其对象进行调用时才加载
例:
A.应用于对象关联实例(默认是延迟加载)
<many-to-one name=".." lazy="true/false" .../>
B.应用于对象关联集合(默认是延迟加载)
<set name=".." lazy="true/false" ... >
....
</set>
对于延迟加载,需要注意的时,对延迟对象的使用必须在Session关闭之前进行,Hibernate的 LazyInitalizationException往往就是由于在Session的生命期外使用了延迟加载的对象。当我们进行Web开发时,可以使用 OpenSessionInView模式,当请求开始时打开session,当请求响应结束时才关闭session,不过,在使用 OpenSessionInView模式时,需要注意如果响应时间比较长(业务比较复杂或者客户端是低速网络),将Session资源(也就是数据库的连 接)占用太久的话可以会导致资源耗尽
3)抓取粒度
抓取粒度指的是对象在关联关系之间被导航时一次预先加载的数量,Hibernate程序的性能比较差往往就在于没有对抓取粒度仔细考虑,当加载一个列表并在列表中的每个对象中对其关联进行导航时,往往导致N+1条SQL语句查询。
例:
A.应用于对象关联实例(默认为1),注意,对对象关联实例的设 置是在被关联的对象之上的,譬如
class User
{
Group g;
}
那么抓取粒度应该在Group的配置文件之上,见下
<class name="Group" table="group" batch-size="..">
...
</class>
对该值并没有一个约定俗成的值,根据情况而定,如果被关联表数据比较少,则可以设置地小一些,3-20,如果比较大则可以设到30-50,注意的时候,并不是越多越好,当其值超过50之后,对性能并没有多大改善但却无谓地消耗内存
假设有如下例子:
List<User> users = query.list();
如果有20个User,并对这20个User及其Group进行遍历,如果不设置batch-size(即batch-size="1"),则在最糟糕的情况
下,需要1 + 20条SQL语句,如果设置batch-size="10",则最好的情况下只需要1 + 2条SQL语句
B.应用于对象关联集合(默认为1)
<set name=".." batch-size="" ... >
....
</set>
2.二级缓存
Hibernate 对数据的缓存包括两个级:一级缓存,在Session的级别上进行,主要是对象缓存,以其id为键保存对象,在Session的生命期间存在;二级缓存, 在SessionFactory的级别上进行,有对象缓存和查询缓存,查询缓存以查询条件为键保存查询结果,在SessionFactory的生命期间存 在。默认地,Hibernate只启用一级缓存,通过正确地使用二级缓存,往往可以获得意想不到的性能。
1)对象缓存:
当抓取一个对象之后,Hiberate将其以id为键缓存起来,当下次碰到抓取id相同的对象时,可以使用如下配置
方法1:在缓存对象上配置
<class ...>
<cache useage="read-only/write/...." regions="group" />
</class>
useage 表示使用什么类型的缓存,譬如只读缓存、读写缓存等等(具体参见Hibernate参考指南),值得注意的时,有部分缓存在Hibernate的实现中不 支持读写缓存,譬如JBossCache在Hibernate的实现中只是一种只读缓存,具体缓存实现对缓存类型的支持情况,可以参见 org.hibernate.cache包
regions表示缓存分块,大部分的缓存实现往往对缓存进行分块,该部分是可选的,详细参见各缓存实现
方法2:在hibernate.cfg.xml中配置
<cache class=".." useage=".." regions=".."/>
我认为第二种更好,可以统一管理
2)查询缓存
查询时候将查询结果以查询条件为键保存起来,需要配置如下
A.在hibernate.cfg.xml中配置(启用查询缓存)
<property name="hibernate.cache.use_query_cache">true</property> (前面的属性名可参见常量
org.hibernate.cfg.Enviroment.USE_QUERY_CACHE)
B.程序
query.setCacheable(true);
query.setCacheRegions(...);
需要注意的是,查询缓存与对象缓存要结合更有效,因为查询缓存仅缓存查询结果列表的主键数据
一般情况下在开发中,对一些比较稳定而又被频繁引用的数据,譬如数据字典之类的,将其进行二级缓存,对一些查询条件和查询数据变化不频繁而又常常被使用的 查询,将其进行二级缓存。由于二级缓存是放在内存中,而且Hibernate的缓存不是弱引用缓存(WeekReference),所以注意不要将大块的 数据放入其中,否则可能会被内存造成比较大的压力。
3.批量数据操作
当进行大批量数据操作(几万甚至几十几百万)时,需要注意两点,一,批量提交,二,及时清除不需要的一级缓存数据
1)所谓的批量提交,就是不要频繁使用session的flush,每一次进行flush,Hibernate将PO数据于数据库进行同步,对于海量级数 据操作来说是性能灾难(同时提交几千条数据和提交一条数据flush一次性能差别可能会是几十倍的差异)。一般将数据操作放在事务中,当事务提交时 Hibernate自动帮你进行flush操作。
2)及时清除不需要的一级缓存数据:由于Hibernate默认采用一级缓存,而在 session的生命期间,所有数据抓取之后会放入一级缓存中,而当数据规模比较庞大时,抓取到内存中的数据会让内存压力非常大,一般分批操作数据,被一 次操作之后将一级缓存清除,譬如
session.clear(User.class)
4.杂项
dynamic-insert,dynamic-update,动态插入和动态更新,指的是让Hibernate插入数据时仅插入非空数据,当修改数据时只修改变化的数据,譬如对于
class User
{
id
username
password
}
如果u.id=1, u.username="ayufox",u.password=null,那么如果不设置动态插入,则其sql语句是 insert into users(id, username, password) values (1, 'ayufox', '),如果设置则其 sql语句是insert into users(username) valeus('ayufox')
在如上的情况下,如果修改 u.password='11',那么如果不设置动态更新,则sql语句为update users set username='ayufox', password='11' where id = 1,如果设置则为update user set password='11' where d = 1
设置是在class的映射文件中,如下
<class name="User" table="users" dynamic-insert="true/false" dynamic-update="true/false" ...>
</class>
  该设置对性能的提升比较有限


在处理大数据量时,会有大量的数据缓冲保存在 Session 的一级缓存中,这缓存大太时会严重显示性能,所以在使用 Hibernate 处理大数据量的,可以使用 session.clear() 或者 session. Evict(Object) 在处理过程中,清除全部的缓存或者清除某个对象。

2) 对大数据量查询时,慎用 list() 或者 iterator() 返回查询结果,
1. 使用 List() 返回结果时, Hibernate 会所有查询结果初始化为持久化对象,结果集较大时,会占用很多的处理时间。
2. 而使用 iterator() 返回结果时,在每次调用 iterator.next() 返回对象并使用对象时, Hibernate 才调用查询将对应的对象初始化,对于大数据量时,每调用一次查询都会花费较多的时间。当结果集较大,但是含有较大量相同的数据,或者结果集不是全部都会使 用时,使用 iterator() 才有优势。
3. 对于大数据量,使用 qry.scroll() 可以得到较好的处理速度以及性能。而且直接对结果集向前向后滚动。

3) 对于关联操作, Hibernate 虽然可以表达复杂的数据关系,但请慎用,使数据关系较为简单时会得到较好的效率,特别是较深层次的关联时,性能会很差。

4) 对含有关联的 PO (持久化对象)时,若 default-cascade="all" 或者 “save-update” ,新增 PO 时,请注意对 PO 中的集合的赋值操作,因为有可能使得多执行一次 update 操作。

5) 在一对多、多对一的关系中,使用延迟加载机制,会使不少的对象在使用时 才 会初始化,这样可使得节省内存空间以及减少数据库的负荷,而且若 PO 中的集合没有被使用时,就可减少互数据库的交互从而减少处理时间。


6) 对于大数据量新增、修改、删除操作或者是对大数据量的查询,与数据库的交互次数是决定处理时间的最重要因素,减少交互的次数是提升效率的最好途径,所以在 开发过程中,请将 show_sql 设置为 true ,深入了解 Hibernate 的处理过程,尝试不同的方式,可以使得效率提升。


7) Hibernate 是以 JDBC 为基础,但是 Hibernate 是对 JDBC 的优化,其中使用 Hibernate 的缓冲机制会使性能提升,如使用二级缓存以及查询缓存,若命中率较高明,性能会是到大幅提升。

8) Hibernate 可以通过设置 hibernate.jdbc.fetch_size , hibernate.jdbc.batch_size 等属性,对 Hibernate 进行优化。
9) 不过值得注意的是,一些数据库提供的主键生成机制在效率上未必最佳,大量并发 insert 数据时可能会引起表之间的互锁。数据库提供的主键生成机制,往往是通过在一个内部表中保存当前主键状态(如对于自增型主键而言,此内部表中就维护着当前的 最大值和递增量),之后每次插入数据会读取这个最大值,然后加上递增量作为新记录的主键,之后再把这个新的最大值更新回内部表中,这样,一次 Insert 操作可能导致数据库内部多次表读写操作,同时伴随的还有数据的加锁解锁操作,这对性能产生了较大影响。
因此,对于并发 Insert 要求较高的系统,推荐采用 uuid.hex 作为主键生成机制。
10) Dynamic Update 如果选定,则生成 Update SQL 时不包含未发生变动的字段属性,这样可以在一定程度上提升 SQL 执行效能 . Dynamic Insert 如果选定,则生成 Insert SQL 时不包含未发生变动的字段属性,这样可以在一定程度上提升 SQL 执行效能
11) 在编写代码的时候请,对将 POJO 的 getter/setter 方法设定为 public ,如果设定为 private , Hibernate 将无法对属性的存取进行优化,只能转而采用传统的反射机制进行操作,这将导致大量的性能开销(特别是在 1.4 之前的 Sun JDK 版本以及 IBM JDK 中,反射所带来的系统开销相当可观)。
12) 在 one-to-many 关系中,将 many 一方设为主动方( inverse=false )将有助性能的改善
13) 由于多对多关联的性能不佳(由于引入了中间表,一次读取操作需要反复数次查询),因此在设计中应该避免大量使用 .
14) Hibernate 支持两种锁机制:即通常所说的“悲观锁( Pessimistic Locking )”和“乐观锁( Optimistic Locking )”。 悲观锁带来 数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。乐观锁机制在一定程度上解决了这个问题.乐观锁机制避免了长事务中的数据库加锁开 销,大大提升了大并发量下的系统整体性能表现。


------------------------------------


一。 inverse = ?
inverse=false(default)
用于单向one-to-many关联
parent.getChildren().add(child) // insert child
parent.getChildren().delete(child) // delete child
inverse=true
用于双向one-to-many关联
child.setParent(parent); session.save(child) // insert child
session.delete(child)
在分层结构的体系中
parentDao, childDao对于CRUD的封装导致往往直接通过session接口持久化对象,而很少通过关联对象可达性

二。 one-to-many关系
单向关系还是双向关系?
parent.getChildren().add(child)对集合的触及操作会导致lazy的集合初始化,在没有对集合配置二级缓存的情况下,应避免此类操作
select * from child where parent_id = xxx;
性能口诀:
1. 一般情况下避免使用单向关联,尽量使用双向关联
2. 使用双向关联,inverse=“true”
3. 在分层结构中通过DAO接口用session直接持久化对象,避免通过关联关系进行可达性持久化



三。many-to-one关系
单向many-to-one表达了外键存储方
灵活运用many-to-one可以避免一些不必要的性能问题
many-to-one表达的含义是:0..n : 1,many可以是0,可以是1,也可以是n,也就是说many-to-one可以表达一对多,一对一,多对一关系
因此可以配置双向many-to-one关系,例如:
1. 一桌四人打麻将,麻将席位和打麻将的人是什么关系?是双向many-to-one的关系

四。one-to-one
通过主键进行关联
相当于把大表拆分为多个小表
例如把大字段单独拆分出来,以提高数据库操作的性能
Hibernate的one-to-one似乎无法lazy,必须通过bytecode enhancement

五。集合List/Bag/Set
one-to-many
1. List需要维护index column,不能被用于双向关联,必须inverse=“false”,被谨慎的使用在某些稀有的场合

2. Bag/Set语义上没有区别
3. 我个人比较喜欢使用Bag
many-to-many
1. Bag和Set语义有区别
2。 建议使用Set

六。集合的过滤
1. children = session.createFilter(parent.getChildren(), “where this.age > 5 and this.age < 10”).list()
针对一对多关联当中的集合元素非常庞大的情况,特别适合于庞大集合的分页:
session.createFilter(parent.getChildren(),“”).setFirstResult(0).setMaxResults(10).list();
在hibernate 中用 super.getSession().createFilter( , )

七。继承关系当中的隐式多态
HQL: from Object
1. 把所有数据库表全部查询出来
2. polymorphism=“implicit”(default)将当前对象,和对象所有继承子类全部一次性取出
3. polymorphism=“explicit”,只取出当前查询对象

八。Hibernate二级缓存
著名的n+1问题:from Child,然后在页面上面显示每个子类的父类信息,就会导致n条对parent表的查询:
select * from parent where id = ?
.......................
select * from parent where id = ?
解决方案
1. eager fetch
2. 二级缓存

九。inverse和二级缓存的关系
当使用集合缓存的情况下:
1. inverse=“false”,通过parent.getChildren()来操作,Hibernate维护集合缓存
2. inverse=“true”,直接对child进行操作,未能维护集合缓存!导致缓存脏数据
3. 双向关联,inverse=“true”的情况下应避免使用集合缓存

十。Hibernate二级缓存是提升web应用性能的法宝
OLTP类型的web应用,由于应用服务器端可以进行群集水平扩展,最终的系统瓶颈总是逃不开数据库访问;

哪个框架能够最大限度减少数据库访问,降低数据库访问压力, 哪个框架提供的性能就更高;针对数据库的缓存策略:
1. 对象缓存:细颗粒度,针对表的记录级别,透明化访问,在不改变程序代码的情况下可以极大提升web应用的性能。对象缓存是ORM的制胜法宝。
2. 对象缓存的优劣取决于框架实现的水平,Hibernate是目前已知对象缓存最强大的开源ORM
3. 查询缓存:粗颗粒度,针对查询结果集,应用于数据实时化要求不高的场合

十一。应用场合决定了系统架构
一、是否需要ORM
Hibernate or iBATIS?
二、采用ORM决定了数据库设计
Hibernate:
倾向于细颗粒度的设计,面向对象,将大表拆分为多个关联关系的小表,消除冗余column,通过二级缓存提升性能(DBA比较忌讳关联关系的出现,但是 ORM的缓存将突破关联关系的性能瓶颈);Hibernate的性能瓶颈不在于关联关系,而在于大表的操作
iBATIS:
倾向于粗颗粒度设计,面向关系,尽量把表合并,通过表column冗余,消除关联关系。无有效缓存手段。iBATIS的性能瓶颈不在于大表操作,而在于关联关系。

总结:
性能口诀
1、使用双向一对多关联,不使用单向一对多
2、灵活使用单向多对一关联
3、不用一对一,用多对一取代
4、配置对象缓存,不使用集合缓存
5、一对多集合使用Bag,多对多集合使用Set
6、继承类使用显式多态
7、表字段要少,表关联不要怕多,有二级缓存撑腰



最近开始留意项目中的Hibernate的性能问题,希望可以抽出时间学习一下hiberante的性能优化。主要是对数据库连接池技术、hibernate二级缓存、hibernate的配置优化等问题进行学习!


1.关联关系:
普通的关联关系:是不包括一个连接表,也就是中间表如:
create table Person(personId bigint not null primary key,addressId bigint not null)
create table Address(addressId bigint not null primary key)
也就是不会还有一个关系表如:
create table Person(personId bigint not null primary key)
create table Address(addressId bigint not null primary key)
create table PersonAddress(personId bigint not null,ddressId bigint not null primary key)


单向many-to-one关联是最常见的,而单向one-to-many是不常见的


2. inner join (内连接)
left (outer) join (左外连接)
right (outer) join (右外连接)
full join (全连接,并不常用)


3.小技巧:
统计结果数目:
(Integer)session.iterator("select count(*) from ..").next()).intValue();
根据一个集合大小来排序:
select user.id,user.name
from User as user.name
left join user.messages msg
group by user.id,user.name
having count(msg)>=1

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics