文档章节

hibernate4性能之并发和锁机制

dong_zq
 dong_zq
发布于 2016/08/30 15:46
字数 3073
阅读 10
收藏 0

数据库事务的定义

数据库事务(Database Transaction),是指作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。

● 原子性(atomic),事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行

● 一致性(consistent),事务在完成时,必须使所有的数据都保持一致状态。

● 隔离性(insulation),由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。

● 持久性(Duration),事务完成之后,它对于系统的影响是永久性的。

数据库事务并发可能带来的问题

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

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

● 脏读(dirty read):如果第二个事务查询到第一个事务还未提交的更新数据,形成脏读。因为第一个事务你还不知道是否提交,所以数据不一定是正确的。

● 虚读(phantom read):一个事务执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,只是另一个事务在这两次查询中间插入或者删除了数据造成的。

● 不可重复读(unrepeated read):一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任。

● 第二类丢失更新(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使用悲观锁十分容易,但实际应用中悲观锁是很少被使用的,因为它大大限制了并发性:

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

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的乐观锁
配置基于version的乐观锁:
[html] view plaincopyprint?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  3.   
  4. <hibernate-mapping>  
  5.     <classnameclassname="com.bzu.hibernate.pojos.People"table="people">  
  6.         <idnameidname="id"type="string">  
  7.             <columnnamecolumnname="id"></column>  
  8.             <generatorclassgeneratorclass="uuid"></generator>  
  9.         </id>  
  10.         
  11.         <!--version标签用于指定表示版本号的字段信息-->  
  12.         <versionnameversionname="version"column="version"type="integer"></version>  
  13.   
  14.         <propertynamepropertyname="name"column="name"type="string"></property>  
  15.         
  16.     </class>  
  17. </hibernate-mapping>  
 
注:不要忘记在实体类添加属性version
下面我们就模拟多个session,基于version的来进行一下测试:
 
  1. /*
  2.      * 模拟多个session操作student数据表 
    
  3.      */  
    
  4.     Session session1=sessionFactory.openSession();  
    
  5.     Session session2=sessionFactory.openSession();  
    
  6.     Studentstu1=(Student)session1.createQuery("from Student s wheres.name='tom11'").uniqueResult();  
    
  7.     Studentstu2=(Student)session2.createQuery("from Student s wheres.name='tom11'").uniqueResult();  
    
  8.     //这时候,两个版本号是相同的  
    
  9.    System.out.println("v1="+stu1.getVersion()+"--v2="+stu2.getVersion());  
    
  10.     Transactiontx1=session1.beginTransaction();  
    
  11.    stu1.setName("session1");  
    
  12.     tx1.commit();  
    
  13.     //这时候,两个版本号是不同的,其中一个的版本号递增了  
    
  14.    System.out.println("v1="+stu1.getVersion()+"--v2="+stu2.getVersion());  
    
  15.     Transactiontx2=session2.beginTransaction();  
    
  16.    stu2.setName("session2");  
    
  17.     tx2.commit(); 
    


运行结果:
Hibernate: insert into studentVersion (ver, name,id) values (?, ?, ?)
Hibernate: select student0_.id as id0_, student0_.ver as ver0_, student0_.nameas name0_ from studentVersion student0_ where student0_.name='tom11'
Hibernate: select student0_.id as id0_, student0_.ver as ver0_, student0_.nameas name0_ from studentVersion student0_ where student0_.name='tom11'
v1=0--v2=0
Hibernate: update studentVersion set ver=?, name=? where id=? and ver=?
v1=1--v2=0
Hibernate: update studentVersion set ver=?, name=? where id=? and ver=?
Exception in thread "main" org.hibernate.StaleObjectStateException:Row was updated or deleted by another transaction (or unsaved-value mapping wasincorrect): [Version.Student#4028818316cd6b460116cd6b50830001]
     可以看到,第二个“用户”session2修改数据时候,记录的版本号已经被session1更新过了,所以抛出了红色的异常,我们可以在实际应用中处理这个异常,例如在处理中重新读取数据库中的数据,同时将目前的数据与数据库中的数据展示出来,让使用者有机会比较一下,或者设计程序自动读取新的数据
● 基于timestamp
配置基于timestamp的乐观锁:
[html] view plaincopyprint?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  3.   
  4. <hibernate-mapping>  
  5.     <classnameclassname="com.suxiaolei.hibernate.pojos.People"table="people">  
  6.         <id name="id"type="string">  
  7.             <column name="id"></column>  
  8.             <generator class="uuid"></generator>  
  9.         </id>  
  10.         
  11.         <!--timestamp标签用于指定表示版本号的字段信息-->  
  12.         <timestamp name="updateDate"column="updateDate"></timestamp>  
  13.   
  14.         <propertynamepropertyname="name"column="name"type="string"></property>  
  15.   
  16.   
  17.     </class>  
  18. </hibernate-mapping>  

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

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

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

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

[html] view plain copy
  1. <filter>  
  2.       <filter-name>HibernateOpenSessionInViewFilter</filter-name>  
  3.       <filter-class>  
  4.             org.springframework.orm.hibernate3.support.OpenSessionInViewFilter  
  5.       </filter-class>  
  6. </filter>  

© 著作权归作者所有

共有 人打赏支持
dong_zq
粉丝 0
博文 10
码字总数 5386
作品 0
郑州
高级程序员
私信 提问
史上最简单的Hibernate4视频教程(附源码和笔记)

Hibernate4是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合...

2846613430
2016/04/08
1K
0
多版本并发控制(MVCC)在分布式系统中的应用

问题 最近项目中遇到了一个分布式系统的并发控制问题。该问题可以抽象为:某分布式系统由一个数据中心D和若干业务处理中心L1,L2 … Ln组成;D本质上是一个key-value存储,它对外提供基于HTT...

虫虫
2012/03/15
1K
0
SQL优化之一则MySQL中的DELETE、UPDATE 子查询的锁机制失效案例

前言 开发与维护人员避免不了与 in/exists、not in/not exists 子查询打交道,接触过的人可能知道 in/exists、not in/not exists 相关子查询会使 SELECT 查询变慢,没有 join 连接效率,却不...

全部原谅
2018/07/27
0
0
4种常用Java线程锁的特点,性能比较及使用场景

多个线程同时对同一个对象进行读写操作,很容易会出现一些难以预料的问题。所以很多时候我们需要给代码块加锁,同一时刻只允许一个线程对某个对象进行操作。多线程之所以会容易引发一些难以发现...

mikechen优知
03/10
0
0
大数据下高并发的处理详解

对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了。而并发问题是绝大部分的程序员头疼的问题,但话又说回来了,既然逃避不掉,那我们就要想想应对措...

商者
2016/07/18
33
0

没有更多内容

加载失败,请刷新页面

加载更多

Confluence 6 升级中的一些常见问题

升级的时候遇到了问题了吗? 如果你想尝试重新进行升级的话,你需要首先重新恢复老的备份。不要尝试再次对 Confluence 进行升级或者在升级失败后重新启动老的 Confluence。 在升级过程中的一...

honeymoose
今天
2
0
C++随笔(四)Nuget打包

首先把自己编译好的包全部准备到一个文件夹 像这样 接下来新建一个文本文档,后缀名叫.nuspec 填写内容 <?xml version="1.0"?><package xmlns="http://schemas.microsoft.com/packaging/201......

Pulsar-V
今天
2
0
再谈使用开源软件搭建数据分析平台

三年前,我写了这篇博客使用开源软件快速搭建数据分析平台, 当时收到了许多的反馈,有50个点赞和300+的收藏。到现在我还能收到一些关于dataplay2的问题。在过去的三年,开源社区和新技术的发...

naughty
今天
3
0
Python3的日期和时间

python 中处理日期时间数据通常使用datetime和time库 因为这两个库中的一些功能有些重复,所以,首先我们来比较一下这两个库的区别,这可以帮助我们在适当的情况下时候合适的库。 在Python文...

编程老陆
今天
2
0
分布式面试整理

并发和并行 并行是两个任务同时进行,而并发呢,则是一会做一个任务一会又切换做另一个任务。 临界区 临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用,但是每一次,只能有...

群星纪元
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部