悲观锁与乐观锁

在事务处理中,我经常要用到锁机制来保证事物的排他性。比如在对某个数据进行操作的时候,不希望其他事物同时对其进行操作,产生冲突。

典型的冲突有:丢失更新,读取脏数据

并发控制机制:悲观锁(pessimistic)和乐观锁(optimistic).

悲观锁:假定会发生并发冲突,则屏蔽一切可能违反数据完整性的操作

乐观锁:假定不会发生并发冲突,只在数据提交时检查是否违反了数据完整性(不能解决脏读问题)

悲观锁:select count(*) from user where name="x" for update

上面这条语句在事务提交之前,其他任何试图修改该记录的操作都被阻止。

Hibernate的悲观锁机制是基于数据库本身的锁机制实现的。

Query query = session.createQuery(sql);

query.setLockMode("user",LockMoe.UPGREAD)//对返回的所有User枷锁

hibernate的加锁模式:

LockMode.NONE:无锁

LockMode.WRITE:在insert和update时自动获取

LockMode.READ:读取时获取

以上这三种锁机制一般由 Hibernate 内部使用,如 Hibernate 为了保证 Update
过程中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE 锁。

LockMode.UPGRADE:利用数据库的for update枷锁

 LockMode. UPGRADE_NOWAIT  Oracle 的特定实现,利用 Oracle  for
update nowait
 子句实现加锁。 

加锁一般是这样实现的:

query.setLockMode();

Criteria.setLockMode()

Session.lock()

切记所有的加锁要在查询之前,也就是hibernate生成sql之前,否则数据已经通过没有for update的sql语句加载进来,在加锁也就无用。

在使用悲观锁时,要注意设定超时设置。悲观锁在操作失败后必须得到释放。想象,你已经获得了对象A,但获得对象B之前你不想放弃对象A;但是另一个已经获得了对象B的用户,在获得对象A之前也不想放弃对象B。那么这时候就需要用到超时设置,它可以打破死锁和处理释放锁失败的情况。

乐观锁(optimistic lock)

悲观锁在大多数情况下是依靠数据库本身的锁机制进行加锁,这在很大程度上增加的独占性,从而开销也就增加。特别对于长事务来说,这种开销是不能容忍的。

比如银行管理员对某个用户的账户余额进行更改操作,那么所有用户对账户的操作都将被制止,直到操作员完成,加入操作员还去干了别的事,那么这样的等待将是不能容忍的。

乐观锁则解决了这个问题。乐观锁大多是基于数据库版本(version)机制进行的加锁。即为数据增加一个version字段,对数据的每次操作都将改变version的值。比如A表,表本身有个version字段,并且有相对的值,用户读出A的数据,并完成update,在提交之前对version字段加1,然后提交之前比较表中的version和提交表单的version的值,如果提交的值大,则更新版本将数据更新,否则拒绝操作,因为版本已过期。比如a表,a和b都操作数据,a和b读出时数据的版本一样,但是a先提交的数据此时数据库中的版本号已变,当b再提交时,则操作被拒绝,抛出StaleObjectStateException异常,因为B提交的版本已过期,这样就阻止的并发冲突的发生。

这样看来乐观锁机制避免了长事务中数据库加锁的开销(在a,b读取数据时都没有枷锁)大大提升了大量并发情况下系统的整体效率。

乐观锁机制是在系统内实现的,所以外部系统对数据的操作不受控制,那么当外部系统对数据库进行操作时,就有可能存入脏数据。在 系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。

乐观锁的出发点在于该数据很少会因为并发修改而产生冲突,所以并发修改显得更重要一点

Optimistic-Lock

你可能感兴趣的:(数据库)