`
一日一博
  • 浏览: 227288 次
  • 性别: 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字段吗?

相关推荐

    高并发处理.大数据量

    高并发处理大数据量 ...高并发处理大数据量需要考虑多方面的因素,包括锁机制、乐观锁机制、表拆分、缓存机制、数据备份和恢复、数据分析和挖掘等。只有通过合理的设计和实现,才能保证系统的高性能和可靠性。

    Hibernate 事物隔离级别 深入探究

    在 Hibernate 中,事务隔离级别是指数据库系统提供的一种机制,以解决并发事务带来的问题。为了确保数据库的可靠性和一致性,Hibernate 提供了四种事务隔离级别,分别是 Serializable、Repeatable Read、Read ...

    hibernate基础教程

    Hibernate中使用了一级缓存和二级缓存的机制来提高程序的性能. 一 为什么要使用缓存? 缓存是一块存储区域,可能是一块内存,也可能是一块硬盘.缓存能起到缓冲的作用,把程序经常使用...

    Hibernate+中文文档

    3.4.6. Hibernate的统计(statistics)机制 3.5. 日志 3.6. 实现NamingStrategy 3.7. XML配置文件 3.8. J2EE应用程序服务器的集成 3.8.1. 事务策略配置 3.8.2. JNDI绑定的SessionFactory 3.8.3. 在JTA环境下...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

     16.3.4 批量延迟检索和批量立即检索(使用batch-size属性)  16.3.5 用带子查询的select语句整批量初始化orders集合(fetch属性为“subselect”)  16.3.6 迫切左外连接检索(fetch属性为“join”)  16.4 多对...

    hibernate3.2中文文档(chm格式)

    3.4.6. Hibernate的统计(statistics)机制 3.5. 日志 3.6. 实现NamingStrategy 3.7. XML配置文件 3.8. J2EE应用程序服务器的集成 3.8.1. 事务策略配置 3.8.2. JNDI绑定的SessionFactory 3.8.3. 在JTA环境下...

    HibernateAPI中文版.chm

    3.4.6. Hibernate的统计(statistics)机制 3.5. 日志 3.6. 实现NamingStrategy 3.7. XML配置文件 3.8. J2EE应用程序服务器的集成 3.8.1. 事务策略配置 3.8.2. JNDI绑定的SessionFactory 3.8.3. 在JTA环境下...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part4

     16.3.4 批量延迟检索和批量立即检索(使用batch-size属性)  16.3.5 用带子查询的select语句整批量初始化orders集合(fetch属性为“subselect”)  16.3.6 迫切左外连接检索(fetch属性为“join”)  16.4 多对...

    Hibernate java对象持久化技术.ppt

    Hibernate入门 OR映射技术 通过Hibernate API操纵数据库 检索策略和方式 数据库事务、并发、缓存与性能优化 高级配置

    Hibernate中文详细学习文档

    3.4.6. Hibernate的统计(statistics)机制 3.5. 日志 3.6. 实现NamingStrategy 3.7. XML配置文件 3.8. J2EE应用程序服务器的集成 3.8.1. 事务策略配置 3.8.2. JNDI绑定的SessionFactory 3.8.3. 在JTA环境下...

    Hibernate 中文 html 帮助文档

    3.4.6. Hibernate的统计(statistics)机制 3.5. 日志 3.6. 实现NamingStrategy 3.7. XML配置文件 3.8. J2EE应用程序服务器的集成 3.8.1. 事务策略配置 3.8.2. JNDI绑定的SessionFactory 3.8.3. 在JTA环境下...

    最全Hibernate 参考文档

    1.1. 开始Hibernate之旅 1.2. 第一个持久化类 1.3. 映射cat 1.4. 与Cat同乐 1.5. 结语 2. 架构(Architecture) 2.1. 概况(Overview) 2.2. 实例状态 2.3. JMX整合 2.4. 对JCA的支持 3. 配置 3.1. 可编程的配置方式 ...

    基于Hibernate的在线考试优化设计与实现

    现有的基于Web的在线考试存在的问题是,当...对于并发操作频繁的系统而言,其封装的JDBC数据库连接池技术和良好的二级缓存管理机制使系统性能提升成为可能。本文针对在线考试系统存在的问题,设计并实现了性能优化方案。

    Hibernate教程

    1.1. 开始Hibernate之旅 1.2. 第一个持久化类 1.3. 映射cat 1.4. 与Cat同乐 1.5. 结语 2. Hibernate入门 2.1. 前言 2.2. 第一部分 - 第一个Hibernate程序 2.2.1. 第一个class 2.2.2. 映射文件 2.2.3. ...

    Hibernate_3.2.0_符合Java习惯的关系数据库持久化

    3.4.6. Hibernate的统计(statistics)机制 3.5. 日志 3.6. 实现NamingStrategy 3.7. XML配置文件 3.8. J2EE应用程序服务器的集成 3.8.1. 事务策略配置 3.8.2. JNDI绑定的SessionFactory 3.8.3. 在JTA环境下...

    hibernate 体系结构与配置 参考文档(html)

    11. 事务和并发 11.1. Session和事务范围(transaction scope) 11.1.1. 操作单元(Unit of work) 11.1.2. 长对话 11.1.3. 关注对象标识(Considering object identity) 11.1.4. 常见问题 11.2. 数据库事务声明 ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part3

     16.3.4 批量延迟检索和批量立即检索(使用batch-size属性)  16.3.5 用带子查询的select语句整批量初始化orders集合(fetch属性为“subselect”)  16.3.6 迫切左外连接检索(fetch属性为“join”)  16.4 多对...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part1.rar

     16.3.4 批量延迟检索和批量立即检索(使用batch-size属性)  16.3.5 用带子查询的select语句整批量初始化orders集合(fetch属性为“subselect”)  16.3.6 迫切左外连接检索(fetch属性为“join”)  16.4 多对...

    hibernate3.04中文文档.chm

    1.1. 开始Hibernate之旅 1.2. 第一个持久化类 1.3. 映射cat 1.4. 与Cat同乐 1.5. 结语 2. Hibernate入门 2.1. 前言 2.2. 第一部分 - 第一个Hibernate程序 2.2.1. 第一个class 2.2.2. 映射文件 2.2.3...

Global site tag (gtag.js) - Google Analytics