`
一日一博
  • 浏览: 230425 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

Hibernate4性能之并发和锁机制

阅读更多
数据库事务的定义
数据库事务(Database Transaction),是指作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。
● 原子性(atomic),事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行
● 一致性(consistent),事务在完成时,必须使所有的数据都保持一致状态。
● 隔离性(insulation),由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。
● 持久性(Duration),事务完成之后,它对于系统的影响是永久性的。

数据库事务并发可能带来的问题
如果没有锁定且多个用户同时访问一个数据库,则当他们的事务同时使用相同的数据时可能会发生问题。由于并发操作带来的数据不一致性包括:丢失数据修改、读”脏”数据(脏读)、不可重复读、产生幽灵数据:

假设数据库中有如下一条记录:

第一类丢失更新(lost update):在完全未隔离事务的情况下,两个事物更新同一条数据资源,某一事物异常终止,回滚造成第一个完成的更新也同时丢失。

在T1时刻开启了事务1,T2时刻开启了事务2,在T3时刻事务1从数据库中取出了id="402881e535194b8f0135194b91310001"的数据,T4时刻事务2取出了同一条数据,T5时刻事务1将age字段值更新为30,T6时刻事务2更新age为35并提交了数据,但是T7事务1回滚了事务age最后的值依然为20,事务2的更新丢失了,这种情况就叫做"第一类丢失更新(lost update)"。
脏读(dirty read):如果第二个事务查询到第一个事务还未提交的更新数据,形成脏读。因为第一个事务你还不知道是否提交,所以数据不一定是正确的。

在T1时刻开启了事务1,T2时刻开启了事务2,在T3时刻事务1从数据库中取出了id="402881e535194b8f0135194b91310001"的数据,在T5时刻事务1将age的值更新为30,但是事务还未提交,T6时刻事务2读取同一条记录,获得age的值为30,但是事务1还未提交,若在T7时刻事务1回滚了事务2的数据就是错误的数据(脏数据),这种情况叫做" 脏读(dirty read)"。
虚读(phantom read):一个事务执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,只是另一个事务在这两次查询中间插入或者删除了数据造成的。

在T1时刻开启了事务1,T2时刻开启了事务2,T3时刻事务1从数据库中查询所有记录,记录总共有一条,T4时刻事务2向数据库中插入一条记录,T6时刻事务2提交事务。T7事务1再次查询数据数据时,记录变成两条了。这种情况是"虚读(phantom read)"。
不可重复读(unrepeated read):一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任。

在T1时刻开启了事务1,T2时刻开启了事务2,在T3时刻事务1从数据库中取出了id="402881e535194b8f0135194b91310001"的数据,此时age=20,T4时刻事务2查询同一条数据,T5事务2更新数据age=30,T6时刻事务2提交事务,T7事务1查询同一条数据,发现数据与第一次不一致。这种情况就是"不可重复读(unrepeated read)"。
第二类丢失更新(second lost updates):是不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。

在T1时刻开启了事务1,T2时刻开启了事务2,T3时刻事务1更新数据age=25,T5时刻事务2更新数据age=30,T6时刻提交事务,T7时刻事务2提交事务,把事务1的更新覆盖了。这种情况就是"第二类丢失更新(second lost updates)"。

Hibernate事务隔离级别:(不同数据库对应默认的级别不一样)
为了解决数据库事务并发运行时的各种问题数据库系统提供四种事务隔离级别,在Hibernate的配置文件中可以显示的配置数据库事务隔离级别。每一个隔离级别用一个整数表示:
Serializable 串行化(8)二进制值0001
Repeatable Read 可重复读(4)二进制值0010 MySql默认隔离级别
Read Commited 可读已提交(2)二进制值0100 Oracle默认级别
Read Uncommited 可读未提交(1)二进制值1000
在hibernate.cfg.xml中使用hibernate.connection.isolation参数配置数据库事务隔离级别。

每一个隔离级别可以解决的问题:
隔离级别第一类丢失更新脏读幻读不可重复读第二类丢失更新
串行化不可能不可能不可能不可能不可能
可重复读不可能不可能可能不可能不可能
可读已提交不可能不可能可能可能可能
可读未提交不可能可能可能可能可能


Hibernate对数据的锁机制:
Hibernate可以利用Query或者Criteria的setLockMode()方法来设定要锁定的表或列(Row)及其锁定模式:
LockMode.NONE:无锁机制;在事务结束时,所有的对象都切换到该模式上来。与session相关联的对象通过调用update()或者saveOrUpdate()脱离该模式
LockMode.WRITE:当更新或者插入一行记录的时候,锁定级别自动设置为LockMode.WRITE
LockMode.READ:当Hibernate在“可重复读”或者是“序列化”数据库隔离级别下读取数据的时候,锁定模式自动设置为LockMode.READ。这种模式也可以通过用户显式指定进行设置。
LockMode.UPGRADE:利用数据库的for update子句加锁
LockMode.UPGRADE_NOWAIT:利用oracle的特定实现for update nowait子句实现

使用悲观锁解决事务并发问题:
悲观锁,正如其名,他总是悲观的认为要操作的数据会有并发访问。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
一个典型的依赖数据库的悲观锁调用:select * from account where name=”Erica” for update这条sql语句锁定了account表中所有符合检索条件(name=”Erica”)的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。悲观锁,也是基于数据库的锁机制实现。
在Hibernate使用悲观锁十分容易,但实际应用中悲观锁是很少被使用的,因为它大大限制了并发性:

T1,T2时刻取款事务和转账事务分别开启,T3事务查询ACCOUNTS表的数据并用悲观锁锁定,T4转账事务也要查询同一条数据,数据库发现该记录已经被前一个事务使用悲观锁锁定了,然后让转账事务等待直到取款事务提交。T6时刻取款事务提交,T7时刻转账事务获取数据。

悲观锁用法参考下面代码实例:

Transaction tx=session.beginTransaction();
//取得持久化User对象,并使用LockMode.UPGRADE模式锁定对象
User user=(User)session.get(User.class,1,LockMode.UPGRADE);
user.setName(“newName”); //更改对象属性,注意并不需要使用session.save(user)
tx.commit();

String hqlStr="from TUser user where user.name='Erica'";
Query query=session.createQuery(hqlStr);  
query.setLockMode("user",LockModel.UPGRADE);  

这样的话,Hibernate会使用select …… for update语句载入User类,并且锁住了这个对象在数据库中的列,直到事务完成(commit()以后)。

使用乐观锁解决事务并发问题
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个"version"字段来实现。
  乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
Hibernate为乐观锁提供了3种实现:
●基于version
●基于timestamp
●为遗留项目添加添加乐观锁

总结
数据库事务应该尽可能的短
这样能降低数据库中的锁争用。数据库长事务会阻止你的应用程序扩展到高的并发负载。因此,假若在用户思考期间让数据库事务开着,直到整个工作单元完成才关闭这个事务,这绝不是一个好的设计。
这就引出一个问题:一个操作单元,也就是一个事务单元的范围应该是多大?
一个操作一个?一个请求一个?一个应用一个?
反模式:session-per-operation
在单个线程中,不要因为一次简单的数据库调用,就打开和关闭一次Session!数据库事务也是如此。也就是说应该禁止自动事务提交(auto-commit)。
session-per-request
最常用的模式是每个请求一个会话。在这种模式下,来自客户端的请求被发送到服务器端,即 Hibernate 持久化层运行的地方,一个新的 Hibernate Session 被打开,并且执行这个操作单元中所有的数据库操作。一旦操作完成(同时对客户端的响应也准备就绪),session 被同步,然后关闭。会话和请求之间的关系是一对一的关系。
Hibernate内置了对“当前session(current session)”的管理,用于简化此模式。你要做的一切就是在服务器端要处理请求的时候,开启事务,在响应发送给客户之前结束事务,通常使用Servelt Filter来完成。
针对这种模式,Spring提供了对Hibernate事务的管理,提供了“一请求一事务”的Filter来利用Http请求与响应来控制session和事务的生命周期。

<filter>
      <filter-name>HibernateOpenSessionInViewFilter</filter-name>
      <filter-class>
            org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
      </filter-class>
</filter>
  • 大小: 4.6 KB
  • 大小: 11.5 KB
  • 大小: 9.6 KB
  • 大小: 10.1 KB
  • 大小: 10.8 KB
  • 大小: 9.8 KB
  • 大小: 77.6 KB
分享到:
评论
5 楼 nanjihuoyan 2013-07-14  
文章很好,就是图片都看不到。
4 楼 一日一博 2012-09-14  
rxin2009 写道
一日一博 写道
rxin2009 写道
文章写的不错,学习了。这里有一个问题想请教下lz:乐观锁的版本记录version的实现方案具体是怎么操作的呢?在建表的时候就为乐观锁额外的添加一个version字段吗?

一般是通过为数据库表增加一个 “version” 字段来实现(我只知道Oracle是这样,其他的没试过)
个人感觉火车站卖票系统就是乐观锁,查票时查到了脏数据(就是另外一些事务还未提交的数据),看到显示屏上有少量剩余票,但点进去去买又买不到。乐观锁原理其实就是给每一条数据加上时间概念的标识,表明我这条数据是什么时候的,可以理解为是相对时间,就是你所说的建表的时候添加version,然后我去取这条数据,比如version这个时候是0015了,那么我操作完这条数据,要update回去时,又去查一遍这个version值,发现如果还是0015,那么我就放心了,我就可以把version变成0016然后提交,因为在我操作这个过程中没人动他,如果比0015大,那说明有人动过他了,我就选择不操作这条数据了。

还有一种方式是直接用时间字段表示,也就是时间戳,就是我提交这条数据时,之前拿到数据时的那个时间值一定要等于我要提交之前这个时刻点的时间值,如果小于当前数据的这个时间戳,那么肯定是有事务在我之前提交了。

其实乐观锁是解决高并发性能的办法。悲观锁更安全,但是并发性能太差,特别是有长事务的时候,并发事务会排很长的队

哦,就是建表的时候添加一个字段,请问下你们项目的数据库表是手动建立的吗(比如先用powerdesign类的工具建好之后,在去生成实体bean)?

这个倒没有规定啊,一般用Hibernate的话,我都直接用eclipse插件反向生成实体类,数据库表都是手动自己去create,说来惭愧,工作之后还真没有用powerdesign这些建模工具了,哈哈,小公司,也没那么多要求。
3 楼 rxin2009 2012-09-14  
一日一博 写道
rxin2009 写道
文章写的不错,学习了。这里有一个问题想请教下lz:乐观锁的版本记录version的实现方案具体是怎么操作的呢?在建表的时候就为乐观锁额外的添加一个version字段吗?

一般是通过为数据库表增加一个 “version” 字段来实现(我只知道Oracle是这样,其他的没试过)
个人感觉火车站卖票系统就是乐观锁,查票时查到了脏数据(就是另外一些事务还未提交的数据),看到显示屏上有少量剩余票,但点进去去买又买不到。乐观锁原理其实就是给每一条数据加上时间概念的标识,表明我这条数据是什么时候的,可以理解为是相对时间,就是你所说的建表的时候添加version,然后我去取这条数据,比如version这个时候是0015了,那么我操作完这条数据,要update回去时,又去查一遍这个version值,发现如果还是0015,那么我就放心了,我就可以把version变成0016然后提交,因为在我操作这个过程中没人动他,如果比0015大,那说明有人动过他了,我就选择不操作这条数据了。

还有一种方式是直接用时间字段表示,也就是时间戳,就是我提交这条数据时,之前拿到数据时的那个时间值一定要等于我要提交之前这个时刻点的时间值,如果小于当前数据的这个时间戳,那么肯定是有事务在我之前提交了。

其实乐观锁是解决高并发性能的办法。悲观锁更安全,但是并发性能太差,特别是有长事务的时候,并发事务会排很长的队

哦,就是建表的时候添加一个字段,请问下你们项目的数据库表是手动建立的吗(比如先用powerdesign类的工具建好之后,在去生成实体bean)?
2 楼 一日一博 2012-09-13  
rxin2009 写道
文章写的不错,学习了。这里有一个问题想请教下lz:乐观锁的版本记录version的实现方案具体是怎么操作的呢?在建表的时候就为乐观锁额外的添加一个version字段吗?

一般是通过为数据库表增加一个 “version” 字段来实现(我只知道Oracle是这样,其他的没试过)
个人感觉火车站卖票系统就是乐观锁,查票时查到了脏数据(就是另外一些事务还未提交的数据),看到显示屏上有少量剩余票,但点进去去买又买不到。乐观锁原理其实就是给每一条数据加上时间概念的标识,表明我这条数据是什么时候的,可以理解为是相对时间,就是你所说的建表的时候添加version,然后我去取这条数据,比如version这个时候是0015了,那么我操作完这条数据,要update回去时,又去查一遍这个version值,发现如果还是0015,那么我就放心了,我就可以把version变成0016然后提交,因为在我操作这个过程中没人动他,如果比0015大,那说明有人动过他了,我就选择不操作这条数据了。

还有一种方式是直接用时间字段表示,也就是时间戳,就是我提交这条数据时,之前拿到数据时的那个时间值一定要等于我要提交之前这个时刻点的时间值,如果小于当前数据的这个时间戳,那么肯定是有事务在我之前提交了。

其实乐观锁是解决高并发性能的办法。悲观锁更安全,但是并发性能太差,特别是有长事务的时候,并发事务会排很长的队
1 楼 rxin2009 2012-09-13  
文章写的不错,学习了。这里有一个问题想请教下lz:乐观锁的版本记录version的实现方案具体是怎么操作的呢?在建表的时候就为乐观锁额外的添加一个version字段吗?

相关推荐

    Hibernate4实战 之第五部分:Hibernate的事务和并发

    ### Hibernate4实战之第五部分:Hibernate的事务与并发 #### 一、事务基础概念 **事务(Transaction)** 是一组逻辑上紧密相连的操作集合。在数据库领域,事务具备ACID特性: - **原子性(Atomicity)**:事务内的所有...

    Hibernate的乐观锁与悲观锁

    **Hibernate**作为一种流行的Java持久层框架,提供了多种机制来处理并发控制问题,其中最常用的就是**乐观锁**和**悲观锁**。本文将详细介绍这两种锁的原理、应用场景以及如何在Hibernate中实现。 #### 二、悲观锁...

    Hibernate乐观锁和悲观锁分析

    这种锁机制依赖于数据库的事务隔离级别和锁机制来实现。在Hibernate中,悲观锁可以通过`Query.setLockMode()`或`Criteria.setLockMode()`方法设置锁模式。例如: ```java String hqlStr = "from TUser as user ...

    Hibernate悲观锁和乐观锁的实现

    在进行Hibernate的测试时,可以创建一个名为`hibernate_test`的项目,编写对应的实体类、映射文件以及测试用例,模拟并发场景,来深入理解并对比悲观锁和乐观锁的差异和效果。 总之,理解并合理运用悲观锁和乐观锁...

    Hibernate中,利用版本管理机制来控制事务并发

    综上所述,Hibernate的版本管理机制是其强大并发控制能力的重要组成部分,通过合理利用这一特性,开发者可以在保证数据一致性的同时,提高系统的并发性能。了解并掌握这些知识点,对于开发高效、稳定的Java应用程序...

    hibernate乐观锁和悲观锁学习

    本文主要讨论的是Hibernate框架中两种锁机制的使用:乐观锁和悲观锁。 首先,让我们深入理解悲观锁(Pessimistic Locking)。悲观锁正如其名字所示,假设并发环境中数据会被频繁修改,所以在整个数据处理过程中,它...

    Hibernate part 14:查询及数据库并发事务

    Hibernate通过事务和锁机制帮助开发者避免这些问题。 9. **实体状态与生命周期**:理解Hibernate中的临时态、持久态、瞬时态和脱管态对于正确处理并发事务至关重要。 10. **最佳实践**:包括合理设计实体关系、...

    hibernate事务管理机制.doc

    【hibernate事务管理机制】是指在使用Hibernate框架进行数据库操作时,如何管理和协调事务的一系列规则和策略。...根据业务场景和并发需求,开发者需要合理选择和使用事务管理机制,以达到最佳的数据一致性和系统性能。

    hibernate的事务核并发

    同时,Hibernate还支持乐观锁和悲观锁机制,通过版本字段或锁定表行的方式,防止并发修改冲突。 ### 使用ThreadLocal和getCurrentSession() 在多线程环境中,如Web应用或EJB容器,为了保证`Session`的线程安全和...

    数据库事务、hibernate悲观锁和乐观锁

    在数据库系统中,有两种主要的锁定机制用于处理并发问题:悲观锁和乐观锁。 悲观锁假设数据在并发环境下可能会发生冲突,因此在读取数据时立即进行锁定,防止其他事务修改。在Hibernate框架中,悲观锁通过`LockMode...

    Hibernate教程26_事务并发处理

    本教程主要聚焦于Hibernate中的事务并发处理,包括悲观锁和乐观锁两种策略,这对于我们理解如何在高并发环境中确保数据的一致性和完整性至关重要。 首先,事务是数据库操作的基本单元,它确保一组操作要么全部成功...

    hibernate的事务和并发资料.pdf

    《Hibernate的事务和并发资料》深入探讨了在Java应用程序中使用Hibernate进行事务管理和并发控制的关键概念。Hibernate作为一款流行的ORM框架,它简化了与数据库的交互,但同时也需要开发者理解其背后的事务处理机制...

    Hibernate 乐观和悲观锁

    标题中的“Hibernate 乐观和悲观锁”涉及到的是Java领域中持久化框架Hibernate中的一种数据并发控制策略。在多线程环境下,为了保证数据的一致性和完整性,数据库管理系统提供了多种锁定机制,其中乐观锁和悲观锁是...

    Hibernate悲观锁与乐观锁

    总结来说,Hibernate的悲观锁和乐观锁是两种不同的并发控制策略,悲观锁倾向于预防并发冲突,适合并发较低但需要保证数据一致性的情景;而乐观锁则在并发较高的情况下更优,通过版本控制减少锁定,但在冲突处理上...

    Hibernate悲观锁与乐观锁案例

    总的来说,Hibernate的悲观锁和乐观锁是处理并发问题的重要工具,开发者需要根据实际情况权衡性能和数据一致性,合理选择并使用合适的锁策略。在实际项目中,还可以结合使用其他的并发控制手段,如事务隔离级别、...

    课程hibernate的事务和并发.pdf

    《课程hibernate的事务和并发》主要探讨了在Hibernate框架中如何管理和处理事务以及并发控制。Hibernate作为一款流行的Java ORM(对象关系映射)工具,其事务处理和并发控制策略对于开发高效、稳定的数据库应用至关...

    Hibernate乐观锁

    使用Hibernate的乐观锁,开发者可以避免长时间持有数据库锁,从而提高系统性能,尤其在并发访问量大的场景下。但是,它并不适用于所有情况。当并发冲突频繁发生时,由于频繁的更新失败,可能会导致应用程序的效率...

    优化Hibernate性能的几点建议

    ### 优化Hibernate性能的几点建议 #### 一、概述 Hibernate作为Java开发中非常流行的ORM(对象关系映射)框架之一,在实现Java对象与数据库之间的转换方面发挥了重要作用。然而,在实际应用过程中,为了提高系统的...

Global site tag (gtag.js) - Google Analytics