总结乐观锁和悲观锁

乐观锁和悲观锁,就是对数据库进行操做时使用的,乐观锁是update是开始,悲观锁是查询记录那一刻开始,二者结束都是commit或者 rollback数据库

悲观锁,一直锁,不让改 乐观锁,只在更新的时候判断一下别人有没有改过这个数据,保证商品只被卖出一次,可使用版本号等机制,能够提升数据吞吐量
并发

 并发控制机制,当一个用户锁住了数据以后,其余用户就不能访问性能

 

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在作操做以前先上锁。spa


乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可使用版本号等机制。乐观锁适用于多读的应用类型,这样能够提升吞吐量,像数据库若是提供相似于write_condition机制的其实都是提供的乐观锁。.net


两种锁各有优缺点,不可认为一种好于另外一种,像乐观锁适用于写比较少的状况下,即冲突真的不多发生的时候,这样能够省去了锁的开销,加大了系统的整个吞吐量。但若是常常产生冲突,上层应用会不断的进行retry,这样反却是下降了性能,因此这种状况下用悲观锁就比较合适。设计

 

一、不管是选择悲观锁策略,仍是乐观锁策略。若是一个对象被上了锁,那么该对象都会受这个锁的控制和影响。若是这个锁是个排它锁,那么其它会话都不能修改它。 对象

 

二、选择悲观锁策略,仍是乐观锁策略,这主要是由应用和业务需求来肯定的。若是你的应用和业务常常会出现从我看到要修改的记录的值,到我修改完成该记录这个时间段内,该记录有较大几率被其它会话所修改。换句话说就是,在我真正去作出修改时,这个记录的值极可能已经与我当初看到的不一样了。那么这时,采起悲观锁策略,也许是更好的。而采起悲观锁策略的一个典型操做就是 select ... for undate。经过这种操做,使得从我一开始查看该记录起,这条记录就被上了排它锁,不容许其它会话再对该记录有任何修改。索引

 

相对的,若是你的业务和应用基本上不多出现这种情景,那么选择乐观锁策略也许就会更好。进程

 

三、这两种策略的核心其实就是持有锁的时间的起止点不一样,悲观锁是从读取记录的那一刻就开始了,而乐观锁只从UPDATE那一刻开始;结束的点二者是同样的,都是发出commit或rollback命令。因此,悲观锁策略会使锁的持续时间更长,而乐观锁的持续时间则较短。其影响就是并发。悲观锁的并发性低于乐观锁。事务

 

四、不管是采用哪一种策略,都要保证数据的完整性。因此,在采用乐观锁策略时,是有可能出现数据的不完整。举例来讲:储户甲的存款余额100元,假设在几乎相同的时刻,发生了两笔业务,业务1为其存入了50元,另外一个业务是其网上购物消费了30元。显然,这两个操做结束后,甲的存款余额应为120元(100+50-30)。但咱们设想一下在数据库层面,可能出现这种状况,当其在银行柜台存入50元时,银行操做员收到了甲存入的50元现金,并经过 select 语句看到甲的当前余额为100元(其发出的指令是下面的语句:

 

select 余额

 

   from 存款余额表

 

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 储户账号=储户甲的银行账号;

 

那么,即便是在乐观锁策略的状况下,依然能够保证数据的正确性和完整性。

 

 

 

 

 

 

 

 

 

 

为何须要锁(并发控制)?

在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。这就是著名的并发性问题。

典型的冲突有:

l 丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失。例如:用户A把值从6改成2,用户B把值从2改成6,则用户A丢失了他的更新。

l 脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取。例如:用户A,B看到的值都是6,用户B把值改成2,用户A读到的值仍为6。

为了解决这些并发带来的问题。 咱们须要引入并发控制机制。

并发控制机制

    最经常使用的处理多用户并发访问的方法是加锁。当一个用户锁住数据库中的某个对象时,其余用户就不能再访问该对象。加锁对并发访问的影响体如今锁的粒度上。好比,放在一个表上的锁限制对整个表的并发访问;放在数据页上的锁限制了对整个数据页的访问;放在行上的锁只限制对该行的并发访问。可见行锁粒度最小,并发访问最好,页锁粒度最大,表锁介于2者之间。

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操做。[1]      悲观锁假定其余用户企图访问或者改变你正在访问、更改的对象的几率是很高的,所以在悲观锁的环境中,在你开始改变此对象以前就将该对象锁住,而且直到你提交了所做的更改以后才释放锁。悲观的缺陷是不管是页锁仍是行锁,加锁的时间可能会很长,这样可能会长时间的限制其余用户的访问,也就是说悲观锁的并发访问性很差。

乐观锁:假设不会发生并发冲突,只在提交操做时检查是否违反数据完整性。[1] 乐观锁不能解决脏读的问题。    乐观锁则认为其余用户企图改变你正在更改的对象的几率是很小的,所以乐观锁直到你准备提交所做的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁能够用较大的锁粒度得到较好的并发访问性能。可是若是第二个用户刚好在第一个用户提交更改以前读取了该对象,那么当他完成了本身的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不从新读取该对象并做出更改。这说明在乐观锁环境中,会增长并发用户读取对象的次数。

 

     从数据库厂商的角度看,使用乐观的页锁是比较好的,尤为在影响不少行的批量操做中能够放比较少的锁,从而下降对资源的需求提升数据库的性能。再考虑汇集索引。在数据库中记录是按照汇集索引的物理顺序存放的。若是使用页锁,当两个用户同时访问更改位于同一数据页上的相邻两行时,其中一个用户必须等待另外一个用户释放锁,这会明显地下降系统的性能。interbase和大多数关系数据库同样,采用的是乐观锁,并且读锁是共享的,写锁是排他的。能够在一个读锁上再放置读锁,但不能再放置写锁;你不能在写锁上再放置任何锁。锁是目前解决多用户并发访问的有效手段。  

乐观锁应用

1.      使用自增加的整数表示数据版本号。更新时检查版本号是否一致,好比数据库中数据版本为6,更新提交时version=6+1,使用该version值(=7)与数据库version+1(=7)做比较,若是相等,则能够更新,若是不等则有可能其余程序已更新该记录,因此返回错误。

2.      使用时间戳来实现.

注:对于以上两种方式,Hibernate自带实现方式:在使用乐观锁的字段前加annotation: @Version, Hibernate在更新时自动校验该字段。

悲观锁应用

须要使用数据库的锁机制,好比SQL SERVER 的TABLOCKX(排它表锁) 此选项被选中时,SQL  Server  将在整个表上置排它锁直至该命令或事务结束。这将防止其余进程读取或修改表中的数据。

SqlServer中使用

Begin Tran
select top 1 @TrainNo=T_NO
         from Train_ticket   with (UPDLOCK)   where S_Flag=0

      update Train_ticket
         set T_Name=user,
             T_Time=getdate(),
             S_Flag=1
         where T_NO=@TrainNo
commit

咱们在查询的时候使用了with (UPDLOCK)选项,在查询记录的时候咱们就对记录加上了更新锁,表示咱们即将对此记录进行更新. 注意更新锁和共享锁是不冲突的,也就是其余用户还能够查询此表的内容,可是和更新锁和排它锁是冲突的.因此其余的更新用户就会阻塞.

结论

在实际生产环境里边,若是并发量不大且不容许脏读,可使用悲观锁解决并发问题;但若是系统的并发很是大的话,悲观锁定会带来很是大的性能问题,因此咱们就要选择乐观锁定的方法.

相关文章
相关标签/搜索