悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在作操做以前先上锁。数据库
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可使用版本号等机制。乐观锁适用于多读的应用类型,这样能够提升吞吐量,像数据库若是提供相似于write_condition机制的其实都是提供的乐观锁。并发
两种锁各有优缺点,不可认为一种好于另外一种,像乐观锁适用于写比较少的状况下,即冲突真的不多发生的时候,这样能够省去了锁的开销,加大了系统的整个吞吐量。但若是常常产生冲突,上层应用会不断的进行retry,这样反却是下降了性能,因此这种状况下用悲观锁就比较合适。性能
一、不管是选择悲观锁策略,仍是乐观锁策略。若是一个对象被上了锁,那么该对象都会受这个锁的控制和影响。若是这个锁是个排它锁,那么其它会话都不能修改它。 spa
二、选择悲观锁策略,仍是乐观锁策略,这主要是由应用和业务需求来肯定的。若是你的应用和业务常常会出现从我看到要修改的记录的值,到我修改完成该记录这 个时间段内,该记录有较大几率被其它会话所修改。换句话说就是,在我真正去作出修改时,这个记录的值极可能已经与我当初看到的不一样了。那么这时,采起悲观 锁策略,也许是更好的。而采起悲观锁策略的一个典型操做就是 select ... for undate。经过这种操做,使得从我一开始查看该记录起,这条记录就被上了排它锁,不容许其它会话再对该记录有任何修改。.net
相对的,若是你的业务和应用基本上不多出现这种情景,那么选择乐观锁策略也许就会更好。设计
三、这两种策略的核心其实就是持有锁的时间的起止点不一样,悲观锁是从读取记录的那一刻就开始了,而乐观锁只从UPDATE那一刻开始;结束的点二者是同样 的,都是发出commit或rollback命令。因此,悲观锁策略会使锁的持续时间更长,而乐观锁的持续时间则较短。其影响就是并发。悲观锁的并发性低 于乐观锁。对象
四、不管是采用哪一种策略,都要保证数据的完整性。因此,在采用乐观锁策略时,是有可能出现数据的不完整。举例来讲:储户甲的存款余额100元,假设在几乎 相同的时刻,发生了两笔业务,业务1为其存入了50元,另外一个业务是其网上购物消费了30元。显然,这两个操做结束后,甲的存款余额应为120元 (100+50-30)。但咱们设想一下在数据库层面,可能出现这种状况,当其在银行柜台存入50元时,银行操做员收到了甲存入的50元现金,并经过 select 语句看到甲的当前余额为100元(其发出的指令是下面的语句:事务
select 余额get
from 存款余额表it
where 储户账号=储户甲的银行账号;)
,接着,发出指令
update 存款余额表
set 余额=150
where 储户账号=储户甲的银行账号;
但就在其看到甲的余额为100元,到其修改甲的余额为150这期间,甲在网上的消费行为致使交易系统已经将甲的余额变成了70元(100-30)并提交 了。当银行员工发出的指令也被提交后,甲的余额变成了150元,至关于甲网上消费的行为没有产生任何扣款。这显然是不正确的,更是要避免出现的。
若是这套系统采用的是悲观锁策略,那么在从银行员工查看甲当前余额的那一个时刻起(这时查询的指令就会是:
select 余额
from 存款余额表
where 储户账号=储户甲的银行账号 for update;)
该记录就已经被锁定了,这时甲网上消费的行为致使的交易系统从甲的账户中扣减的操做就会处于等待状态。直至银行员工提交了相关指令,交易系统才能去扣减甲的钱款。这样,就能够确保甲的账户余额是正确的。
悲观锁的策略显然能够保证业务的正确性和完整性。但再设想一下,若是甲在存款时,银行员工内急,或者储户甲说等一等,我要考虑一下是否再多存一些。那么, 银行员工的操做就不会提交,这时网上交易系统对甲账户的扣款操做就会一直处于等待状态,或者在等待必定时间后,返回一个扣款失败的提示。这对于系统的效率 和客户来讲,都不是一个好的体验。
五、由于考虑到悲观锁策略能够产生的这种问题,因此,咱们在设计应用时,能够采起一些其它方法来避免上述状况的发生。其思想就是在真正提交时,确认要修改的数据没有变化过。主要的方法以下:
(1)、更新时带入原始的数据。
update 存款余额表
set 余额=150
where 储户账号=储户甲的银行账号 and 余额=100;
(2)、在记录上增长修改的时间戳(也可利用ora_rowscn伪列)。即在事务开始时,获取该记录的时间戳,修改时,校验该时间戳,若一致则修改。
六、其实,我上面举的这个例子,若是在业务设计时,选择更新指令为
update 存款余额表
set 余额=余额+50
where 储户账号=储户甲的银行账号;
那么,即便是在乐观锁策略的状况下,依然能够保证数据的正确性和完整性。