Hibernate悲观锁定与乐观锁定区别

平步星云 2010-11-27

Hibernate悲观锁定与乐观锁定区别

为了避免丢失更新,要使用某种锁定策略,共有两种锁定策略:悲观锁定或乐观锁定。

悲观锁定(pessimistic locking):用户在屏幕上修改值之前,这个锁定方法就要起作用。例如,用户一旦有意对他选择的某个特定行(屏幕上可见)执行更新,如单击屏幕上的一个按钮,就会放上一个锁。

悲观锁定仅用于有状态(stateful)或有连接(connected)的环境,这是20世纪90年代中期客户/服务器应用中的一种流行做法。但现在采用有状态方式的连接方法已经不太常见了(不过并没有完全消失),特别是随着20世纪90年代中后期应用服务器的出现,有状态连接更是少见。

悲观锁定的例子:

selectempno,ename,sal

fromemp

whereempno=:empno

andename=:aname

andsal=:sal

  for update nowait

乐观锁定(optimistic locking)即把所有锁定都延迟到即将执行更新之前才作。换句话说,我们会修改屏幕上的信息而不要锁。我们很乐观,认为数据不会被其他用户修改;因此,会等到最后一刻才会去看我们的想法对不对。

这种锁定方法在所有环境下都行得通,但是执行更新的用户“失败”的可能性会增大。这个用户要更新它的数据行时,发现数据已经修改过,所以他必须重头再来。

可以在应用中同时保留旧值和新值,然后在更新数据库时使用如下的更新语句,这是乐观锁定的一种流行实现:

updtaetable

setcolumn1=:new_column1,column2=:new_column2,...

whereprimary_key=:primary_key

andcolumn1=:old_column1

andcolumn2=:old_column2

   ...

这种情况下,我们可能很幸运地更新了一行,但如果另一个人已经修改了数据,我们就会失败。现在我们必须确定下一步要做什么,是让最终用户重新查询这一行现在的新值,然后再重新开始新事物呢(但是这一行有可能又被修改了,这可能会使用户很受打击)?还是根据业务规则更新冲突,试图合并两个更新的值(这需要大量的代码)?

另外,上面的update有可能被阻塞。如果所有应用(会话)都使用乐观锁定,执行更新并提交时,行只会被锁定很短的时间。但如果某些应用使用了悲观锁定,它会在较长时间内持有行上的锁,你可能就会考虑使用select for update nowait,以此来验证行是否被修改,并在即将update之前锁定来避免被另一个会话阻塞。

另外三种实现乐观并发控制的方法:

1、使用版本列的乐观锁定。

对每个要保护的表增加一列,这一列通常是number或date/timestamp列,通常通过行触发器来维护。

在应用中只需保存这个版本列的值,而不用保存所有其他列的值。

为了维护这个版本列,建议把更新逻辑封装到一个存储过程中。另一个实现方法是使用触发器,但是触发器会引入大量开销。

2、使用校验和的乐观锁定

这与前面的版本列方法很相似,在此要使用计数据本身来计算一个“虚拟的”版本列。有很多方法计算散列或校验和。以下是其中的三种:

·OWA_OPT_LOCK.CHECKSUM:(Oracle8.1.5)及以上版本中提供,出现冲突的可能性是1/65536。

·DBMS_DBFUSCATION_TOOLKIT.MD5:(Oracle8.1.7)及以上版本中提供,出现冲突的可能性是1/(3.4E+38),非常小。

·DBMS_CRYPTO.HSAH:(Oracle10.1)及以上版本中提供.

很多编成语言中都提供了一些散列和校验和函数,所以还可以使用数据库之外的散列和校验和函数。

计算散列和校验和是CPU密集型的操作,其计算代价很昂贵,但是其“网络友好性比较好”。

下面的ORA_ROWSCN方法不仅很小(类似于散列),而且计算时不是cpu密集的。

3、使用ORA_ROWSCN的乐观锁定

从Oracle 10.1开始,你还可以使用内置的ORA_ROWSCN函数,其原理与版本列技术很相似,但是可以由Oracle自动执行,而不需要在表中增加额外的列,也不需要额外的代码来更新维护这个值。

 不管是悲观锁定还是乐观锁定都可以利用select for update nowaut查询来验证行未被修改。悲观锁定会在用户有意修改数据那一刻使用这条语句。乐观锁定则在即将在数据库中更新数据时使用这条语句。这样不仅能解决应用中的阻塞问题,还可以修正数据完整性问题。

相关推荐