`
shenzhw
  • 浏览: 62888 次
  • 性别: Icon_minigender_1
  • 来自: 福州
文章分类
社区版块
存档分类
最新评论

悲观锁与乐观锁-备忘

阅读更多
文章转自网上好像是玉米田的,忘记了

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

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 )
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依
靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库
性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进
行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过
程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作
员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几
百上千个并发,这样的情况将导致怎样的后果。
乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本
( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于
数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来
实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提
交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据
版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个
version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。
1 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50
( $100-$50 )。
2 在操作员 A 操作的过程中,操作员 B 也读入此用户信息( version=1 ),并
从其帐户余额中扣除 $20 ( $100-$20 )。
3 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣
除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大
于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
4 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数
据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的
数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须大于记
录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。
这样,就避免了操作员 B 用基于 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">
</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 异
常,并指出版本检查失败,当前事务正在试图提交一个过期数据。通过捕捉这个异常,我
们就可以在乐观锁校验失败时进行相应处理
分享到:
评论

相关推荐

    微信小程序源码-备忘录.zip

    微信小程序源码-备忘录.zip微信小程序源码-备忘录.zip微信小程序源码-备忘录.zip微信小程序源码-备忘录.zip微信小程序源码-备忘录.zip微信小程序源码-备忘录.zip微信小程序源码-备忘录.zip微信小程序源码-备忘录.zip...

    微信小程序-------备忘录

    微信小程序备忘录,备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录备忘录...

    Android课程设计--便捷备忘录

    **Android课程设计--便捷备忘录** 在移动应用开发领域,Android系统因其开源特性与广泛的应用,成为开发者的重要平台。本项目"Android课程设计--便捷备忘录"旨在教授学生如何利用Android Studio进行应用程序开发,...

    微信小程序实例-备忘录(毕业设计).zip

    微信小程序毕业设计,微信小程序实例-备忘录(毕业设计) 微信小程序毕业设计,微信小程序实例-备忘录(毕业设计) 微信小程序毕业设计,微信小程序实例-备忘录(毕业设计) 微信小程序毕业设计,微信小程序实例-...

    Android程序源码--备忘录

    如果备忘录应用需要与其他应用共享数据,可以考虑使用ContentProvider。它为数据提供了统一的访问接口,其他应用可以通过URI来查询或修改备忘录内容。 7. **通知和提醒**: 为了增强用户体验,备忘录应用可能还...

    SQLite的小例子--备忘录

    8. 锁机制:SQLite提供了行级锁,保证了在多用户环境下的并发访问安全性。 9. SQL游标和结果集:在编程中,我们通常会使用游标对象来遍历查询结果,比如Python的sqlite3模块中的`cursor.execute()`方法和`fetchone...

    效能桌面便签----备忘记事

    《效能桌面便签——高效备忘与记事工具详解》 在我们的日常工作中,有效管理时间、记录待办事项和重要信息是至关重要的。而"效能桌面便签"就是这样一款专为提高工作效率、帮助我们更好地组织和记忆事物的工具。这款...

    微信小程序--备忘录

    1. **创建与编辑备忘**:用户可以创建新的备忘,输入文字内容,添加图片、语音、甚至手绘草图,以丰富备忘的信息表达。 2. **分类管理**:备忘录通常支持对备忘进行分类管理,如工作、学习、生活等,便于用户查找和...

    微信小程序-备忘录-记录代办事项(源码+截图)

    微信小程序实现的简易备忘录,可以记录代办事项,可以标记事项是否完成 微信小程序实现的简易备忘录,可以记录代办事项,可以标记事项是否完成 微信小程序实现的简易备忘录,可以记录代办事项,可以标记事项是否完成...

    MB备忘----2

    【标题】"MB备忘----2"所提及的内容可能与某个名为"MB"的项目或者技术有关,这可能是一个软件开发、系统管理或者是其他IT领域的记事或文档集合。"MB"可能是项目缩写,也可能是特定技术或工具的名称。由于描述中提到...

    备忘录--C++程序

    2. **排序与搜索**:为备忘录添加排序功能(按日期或其他标准),并实现快速查找功能。 3. **界面美化**:可以使用C++的终端控制库,如ncurses,提高命令行界面的用户体验。 通过以上步骤,我们可以构建一个功能...

    设计模式-备忘录

    备忘录模式是一种常用的设计模式,它在软件工程中用于保存对象的状态,以便在需要时恢复到之前的状态。这种模式的主要目标是实现数据的安全存储,同时保持对象的封装性,避免对外部对象直接访问其内部状态。在iOS...

    微信小程序实例-备忘录(附源码&文档).zip

    微信小程序实例-备忘录(附源码&文档) 自己写的微信小程序:备忘录。 功能:列表、Tabar导航、搜索、日记详情、注册、个人中心、登录、头像相关、写日记支持图片上传、客服功能、数字密码锁功能等等。 开源share给...

    微信小程序-备忘录案例

    这个“微信小程序-备忘录案例”是利用微信小程序框架实现的一个功能完备的备忘录应用,旨在帮助开发者熟悉小程序的开发流程和技术栈。 首先,我们要了解微信小程序的基本架构。小程序的开发主要涉及四个部分:JSON...

    微信小程序-备忘录示例代码.zip

    6. **网络请求**:如果备忘录示例涉及数据持久化,可能需要使用微信小程序提供的`wx.request`接口进行网络请求,与服务器进行数据交互。这通常在用户保存备忘时调用,将备忘内容发送到服务器存储。 7. **本地存储**...

    Java设计模式-备忘录

    **Java设计模式——备忘录模式详解** 备忘录模式是一种行为设计模式,它允许对象在不破坏封装性的前提下捕获并存储...同时,备忘录模式与其他设计模式如工厂模式、观察者模式等结合使用,可以构建出更复杂的系统架构。

    行业文档-设计装置-备忘写字笔.zip

    【标签】"行业文档-设计装置-备忘写字笔." 进一步明确了这个文档集合的主题,即与设计行业的装置设计相关,特别是备忘写字笔这一独特的产品。这表明文档可能涵盖了设计流程、材料选择、工艺技术、用户体验研究等多个...

    JAVA-设计模式-行为型模式-备忘录模式

    JAVA-设计模式-行为型模式-备忘录模式

    java课设--个人备忘录管理系统

    在本项目中,"java课设--个人备忘录管理系统"是一个基于Java编程语言实现的课程设计项目,旨在提供一个方便用户记录、管理和检索个人备忘录的应用程序。这个系统通常包括用户界面、数据存储、备忘录的增删改查等功能...

    设计模式-备忘录模式(讲解及其实现代码)

    备忘录模式是一种在软件工程中广泛使用的面向对象设计模式,它主要用来安全地保存对象的状态,以便在需要时能够恢复到先前的状态。备忘录模式的核心思想是封装对象的状态,将其保存到一个独立的对象(备忘录)中,而...

Global site tag (gtag.js) - Google Analytics