乐观锁悲观锁

锁( locking  
业务逻辑的实现过程当中,每每须要保证数据访问的排他性。如在金融系统的日终结算 
处理中,咱们但愿针对某个 cut-off 时间点的数据进行处理,而不但愿在结算进行过程当中 
(多是几秒种,也多是几个小时),数据再发生变化。此时,咱们就须要经过一些机 
制来保证这些数据在某个操做过程当中不会被外界修改,这样的机制,在这里,也就是所谓 
    ,即给咱们选定的目标数据上锁,使其没法被其余程序修改。 
Hibernate
 支持两种锁机制:即一般所说的  悲观锁( Pessimistic Locking  
  乐观锁( Optimistic Locking    
悲观锁( Pessimistic Locking  
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其余事务,以及来自 
外部系统的事务处理)修改持保守态度,所以,在整个数据处理过程当中,将数据处于锁定 
状态。悲观锁的实现,每每依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能 
真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系 
统不会修改数据)。 
一个典型的倚赖数据库的悲观锁调用: 
select * from account where name=”Erica” for update
这条 sql 语句锁定了 account 表中全部符合检索条件( name=”Erica” )的记录。 
本次事务提交以前(事务提交时会释放事务过程当中的锁),外界没法修改这些记录。 
Hibernate
 的悲观锁,也是基于数据库的锁机制实现。 
下面的代码实现了对查询记录的加锁: java

 

String hqlStr =
"from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user",LockMode.UPGRADE); //
 加锁 
List userList = query.list();//
 执行查询,获取数据 
query.setLockMode
 对查询语句中,特定别名所对应的记录进行加锁(咱们为 
TUser
 类指定了一个别名 “user” ),这里也就是对返回的全部 user 记录进行加锁。 
观察运行期 Hibernate 生成的 SQL 语句: 
select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id
as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex
from t_user tuser0_ where (tuser0_.name='Erica' ) for update
这里 Hibernate 经过使用数据库的 for update 子句实现了悲观锁机制。 
Hibernate
 的加锁模式有: 
Ø LockMode.NONE 
 无锁机制。 
Ø LockMode.WRITE 
 Hibernate  Insert  Update 记录的时候会自动 
获取。 
Ø LockMode.READ 
 Hibernate 在读取记录的时候会自动获取。 
以上这三种锁机制通常由 Hibernate 内部使用,如 Hibernate 为了保证 Update
过程当中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE 锁。 
Ø LockMode.UPGRADE 
:利用数据库的 for update 子句加锁。 
Ø LockMode. UPGRADE_NOWAIT 
 Oracle 的特定实现,利用 Oracle  for
update nowait
 子句实现加锁。 
上面这两种锁机制是咱们在应用层较为经常使用的,加锁通常经过如下方法实现: 
Criteria.setLockMode
Query.setLockMode
Session.lock
注意,只有在查询开始以前(也就是 Hiberate 生成 SQL 以前)设定加锁,才会 
真正经过数据库的锁机制进行加锁处理,不然,数据已经经过不包含 for update
子句的 Select SQL 加载进来,所谓数据库加锁也就无从谈起。 
乐观锁( Optimistic Locking  
相对悲观锁而言,乐观锁机制采起了更加宽松的加锁机制。悲观锁大多数状况下依 
靠数据库的锁机制实现,以保证操做最大程度的独占性。但随之而来的就是数据库 
性能的大量开销,特别是对长事务而言,这样的开销每每没法承受。 
如一个金融系统,当某个操做员读取用户的数据,并在读出的用户数据的基础上进 
行修改时(如更改用户账户余额),若是采用悲观锁机制,也就意味着整个操做过 
程中(从操做员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操做 
员中途去煮咖啡的时间),数据库记录始终处于加锁状态,能够想见,若是面对几 sql

百上千个并发,这样的状况将致使怎样的后果。 
乐观锁机制在必定程度上解决了这个问题。乐观锁,大可能是基于数据版本 
 Version )记录机制实现。何谓数据版本?即为数据增长一个版本标识,在基于 
数据库表的版本解决方案中,通常是经过为数据库表增长一个 “version” 字段来 
实现。 
读取出数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时,将提 
交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据 
版本号大于数据库表当前版本号,则予以更新,不然认为是过时数据。 
对于上面修改用户账户信息的例子而言,假设数据库中账户信息表中有一个 
version
 字段,当前值为 1 ;而当前账户余额字段( balance )为 $100  
操做员 此时将其读出( version=1 ),并从其账户余额中扣除 $50
 $100-$50 )。 
在操做员 A 操做的过程当中,操做员 B 也读入此用户信息( version=1 ),并 
从其账户余额中扣除 $20  $100-$20 )。 
操做员 A 完成了修改工做,将数据版本号加一( version=2 ),连同账户扣 
除后余额( balance=$50 ),提交至数据库更新,此时因为提交数据版本大 
于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2  
操做员 B 完成了操做,也将版本号加一( version=2 )试图向数据库提交数 
据( balance=$80 ),但此时比对数据库记录版本时发现,操做员 B 提交的 
数据版本号为 2 ,数据库记录当前版本也为 2 ,不知足  提交版本必须大于记 
录当前版本才能执行更新  的乐观锁策略,所以,操做员 的提交被驳回。 
这样,就避免了操做员 用基于 version=1 的旧数据修改的结果覆盖操做 
 A 的操做结果的可能。 
从上面的例子能够看出,乐观锁机制避免了长事务中的数据库加锁开销(操做员 A
和操做员 B 操做过程当中,都没有对数据库数据加锁),大大提高了大并发量下的系 
统总体性能表现。 
须要注意的是,乐观锁机制每每基于系统中的数据存储逻辑,所以也具有必定的局 
限性,如在上例中,因为乐观锁机制是在咱们的系统中实现,来自外部系统的用户 
余额更新操做不受咱们系统的控制,所以可能会形成脏数据被更新到数据库中。在 
系统设计阶段,咱们应该充分考虑到这些状况出现的可能性,并进行相应调整(如 
将乐观锁策略在数据库存储过程当中实现,对外只开放基于此存储过程的数据更新途 
径,而不是将数据库表直接对外公开)。 
Hibernate 
在其数据访问引擎中内置了乐观锁实现。若是不用考虑外部系统对数 
据库的更新操做,利用 Hibernate 提供的透明化乐观锁实现,将大大提高咱们的 
生产力。 
Hibernate
 中能够经过 class 描述符的 optimistic-lock 属性结合 version
描述符指定。 
如今,咱们为以前示例中的 TUser 加上乐观锁机制。 数据库

1  首先为 TUser  class 描述符添加 optimistic-lock 属性: 
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
optimistic-lock="version"
>
……
</class>
</hibernate-mapping>
optimistic-lock
 属性有以下可选取值: 
Ø none
无乐观锁 
Ø version
经过版本机制实现乐观锁 
Ø dirty
经过检查发生变更过的属性实现乐观锁 
Ø all
经过检查全部属性实现乐观锁 
其中经过 version 实现的乐观锁机制是 Hibernate 官方推荐的乐观锁实现,同时也 
 Hibernate 中,目前惟一在数据对象脱离 Session 发生修改的状况下依然有效的锁机 
制。所以,通常状况下,咱们都选择 version 方式做为 Hibernate 乐观锁实现机制。 
2
  添加一个 Version 属性描述符 
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
optimistic-lock="version"
>
<id
name="id"
column="id"
type="java.lang.Integer"
>
<generator class="native">
session

</generator>
</id>
<version
column="version"
name="version"
type="java.lang.Integer"
/>
……
</class>
</hibernate-mapping>
注意 version 节点必须出如今 ID 节点以后。 
这里咱们声明了一个 version 属性,用于存放用户的版本信息,保存在 TUser 表的 
version
 字段中。 
此时若是咱们尝试编写一段代码,更新 TUser 表中记录数据,如: 
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
List userList = criteria.list();
TUser user =(TUser)userList.get(0);
Transaction tx = session.beginTransaction();
user.setUserType(1); //
 更新 UserType 字段 
tx.commit();
每次对 TUser 进行更新的时候,咱们能够发现,数据库中的 version 都在递增。 
而若是咱们尝试在 tx.commit 以前,启动另一个 Session ,对名为 Erica 的用 
户进行操做,以模拟并发更新时的情形: 
Session session= getSession();
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
Session session2 = getSession();
Criteria criteria2 = session2.createCriteria(TUser.class);
criteria2.add(Expression.eq("name","Erica"));
List userList = criteria.list();
List userList2 = criteria2.list();TUser user =(TUser)userList.get(0);
TUser user2 =(TUser)userList2.get(0);
Transaction tx = session.beginTransaction();
Transaction tx2 = session2.beginTransaction();
user2.setUserType(99);
tx2.commit();
user.setUserType(1);
tx.commit();
执行以上代码,代码将在 tx.commit() 处抛出 StaleObjectStateException  
常,并指出版本检查失败,当前事务正在试图提交一个过时数据。经过捕捉这个异常,我 
们就能够在乐观锁校验失败时进行相应处理 并发

相关文章
相关标签/搜索