java技术研究

统计

留言簿(3)

阅读排行榜

评论排行榜

悲观锁与乐观锁(转)

先申明概念:

1、悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
2、乐观锁( Optimistic Locking )
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

所以悲观锁和乐观锁最大的区别是是否一直锁定资源,悲观锁在事物的全流程锁定数据,乐观锁不锁定数据(用读写锁是阻塞事物,而用乐观锁则会导致回滚。这个是一种事物冲突后的不同锁的表象)。乐观锁的最大特点是在最后检查数据是否被修改,如果已被别人修改过,则回滚数据,避免脏数据。至于事物是否冲突和加锁没有直接联系,该冲突的还是会冲突,不管你加悲观锁和乐观锁都会冲突。

悲观锁和乐观锁都是为了解决丢失更新问题或者是脏读。悲观锁和乐观锁的重点就是是否在读取记录的时候直接上锁。悲观锁的缺点很明显,需要一个持续的数据库连接,这在web应用中已经不适合了。

一个比较清楚的场景

下面这个假设的实际场景可以比较清楚的帮助我们理解这个问题:

a. 假设当当网上用户下单买了本书,这时数据库中有条订单号为001的订单,其中有个status字段是’有效’,表示该订单是有效的;
b. 后台管理人员查询到这条001的订单,并且看到状态是有效的
c. 用户发现下单的时候下错了,于是撤销订单,假设运行这样一条SQL: update order_table set status = ‘取消’ where order_id = 001;
d. 后台管理人员由于在b这步看到状态有效的,这时,虽然用户在c这步已经撤销了订单,可是管理人员并未刷新界面,看到的订单状态还是有效的,于是点击”发货”按钮,将该订单发到物流部门,同时运行类似如下SQL,将订单状态改成已发货:update order_table set status = ‘已发货’ where order_id = 001

观点1:只有冲突非常严重的系统才需要悲观锁;
分析:这是更准确的说法;
“所有悲观锁的做法都适合于状态被修改的概率比较高的情况,具体是否合适则需要根据实际情况判断。”,表达的也是这个意思,不过说法不够准确;的确,之所以用悲观锁就是因为两个用户更新同一条数据的概率高,也就是冲突比较严重的情况下,所以才用悲观锁。

观点2:最后提交前作一次select for update检查,然后再提交update也是一种乐观锁的做法
分析:这是更准确的说法;
的确,这符合传统乐观锁的做法,就是到最后再去检查。但是wiki在解释悲观锁的做法的时候,’It is not appropriate for use in web application development.’, 现在已经很少有悲观锁的做法了,所以我自己将这种二次检查的做法也归为悲观锁的变种,因为这在所有乐观锁里面,做法和悲观锁是最接近的,都是先select for update,然后update

*****除了上面的观点1和观点2是更准确的说法,下面的所有观点都是错误的***********

观点3:这个问题的原因是因为数据库隔离级别是uncommitted read级别;
分析:这个观点是错误的;
这个过程本身就是在read committed隔离级别下发生的,从a到d每一步,尤其是d这步,并不是因为读到了未提交的数据,仅仅是因为用户界面没有刷新[事实上也不可能做自动刷新,这样相当于数据库一发生改变立刻要刷新了,这需要监听数据库了,显然这是简单问题复杂化了];

观点4:悲观锁是指一个用户在更新数据的时候,其他用户不能读取这条记录;也就是update阻塞读才叫悲观锁;
分析:这个观点是错的;
这在db2背景的开发中尤其常见;因为db2默认就是update会阻塞读;但是这是各个数据库对读写的时候上锁的并发处理实现不一样。但这根本不是悲观锁乐观锁的区别。Oracle可以做到写不阻塞读仅仅是因为做了多版本并发控制(Multiversion concurrency control), http://en.wikipedia.org/wiki/Multiversion_concurrency_control;但是在Oracle里面,一样可以做乐观锁和悲观锁的控制。这本质上是应用层面的选择。

观点5:Oracle实际上用的就是乐观锁
分析:这个观点是错的;
前面说了,Oracle的确可以做到写不阻塞读,但是这不是悲观锁和乐观锁的问题。这是因为实现了多版本并发控制。按照wiki的定义,悲观锁和乐观锁是在应用层面选择的。Oracle的应用只要在第二步做了select for update,就是悲观锁的做法;况且Oracle在任何隔离级别下,除了分布式事务两阶段提交的短暂时间,其他所有情况下都不存在写阻塞读的情况,如果按照这个观点的话那Oracle已经不能做悲观锁了-_-

观点6:不需要这么麻烦,只需要在d这步,最后提交更新的时候再做一个普通的select检查一下就可以;[就是double check的做法]
分析:这个观点是错的。
这个做法其实在http://www.hetaoblog.com/database-lost-update-pessimistic-lock/,’3. 传统悲观锁做法的变通’这节已经说明了,如果要这么做的话,仍然需要在最后提交更新前double check的时候做一个select for update, 否则select结束到update提交前的时间仍然有可能记录被修改;

观点7:应该尽可能使用悲观锁;
分析:这个观点是错的;
a. 根据悲观锁的概念,用户在读的时候(b这步)就会将记录锁住,直到更新结束的时候才会将锁释放,所以整个锁的过程时间比较长;
b. 另外,悲观锁需要有一个持续的数据库连接,这在当今的web应用中已经几乎不存在;wiki上也说了, 悲观锁‘is not appropriate for use in web application development.’

所以,现在大部分应用都应该是乐观锁的;
转自:http://zhidao.baidu.com/link?url=MUOUg59oz7-FKwz-zuUviGryfw9J4V63Pd2iWWErorwUpyeL85rznlmYaGDHXjH_ChywA3R1m9XNpx4k7RCCT3rNofjkCxIBYHdsvwr2bVy

posted on 2016-01-05 09:32 小秦 阅读(321) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航: