在事务处理中,我经常要用到锁机制来保证事物的排他性。比如在对某个数据进行操作的时候,不希望其他事物同时对其进行操作,产生冲突。
典型的冲突有:丢失更新,读取脏数据
并发控制机制:悲观锁(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