日期:2014-05-16  浏览次数:20428 次

数据库事务和并发
当处于高并发环境时,有可能多个事务同时作用于某步操作,这将导致事务在不同时间读取到的数据不一致或修改丢失等错误。

为了避免上述错误, 可采用如下策略:
使用完全隔离的事务
  1. serializable 使用完全隔离的事务,事务看起来像是一个接一个地执行。
  2. repeatable 保证每次读取相同的记录能得到相同的结果
  3. read commited 通常情况下,我们应该使用该隔离级别, 并结合使用乐观锁和悲观锁。


乐观锁
其原理就是事务在执行更新操作前先判断更新数据是否被修改。可以通过版本号、时间戳、对象状态等来判断是否已被修改。一般情况下,尽量使用版本号来判断,只在对老系统进行升级且无法使用版本号的前提下, 才会使用时间戳。

关于乐观锁,jdbc和ibatis需要用户自己单独实现。jdo和hibernate无需用户单独实现,只需要简单的配置就可以使用。

在如下情况下,不推荐使用乐观锁:
数据库模式不支持乐观锁(对老系统进行升级)
应用程序必须保证能够更新读取的记录
应用程序要求读取的一致性

悲观锁
悲观锁机制会假设更新冲突总会发生。所以不管冲突是否真的会发生,它都会枷锁。悲观锁能在一定程度上防止读取数据的不一致性。但当有insert操作时,读取结果可能不一致。
用法
select ... for update

补充:关于隔离级别,可以通过spring拦截器或jdbc datasource来实现。


通知并发更新
在业务层捕获/处理dao异常并不是一件明智的事,幸运的是,spring提供了一个扩展,能够自动翻译jdbc、hibernate、jdo抛出的数据访问异常。
但是,spring在处理此操作时有一个缺陷,就是spring无法出由oracle产生的部分错误识别码。如:ORA-00060 ORA-08177等。针对上述情况,我们可以通过扩展
SQLErrorCodeSQLExceptionTranslator来弥补。


使用jdo和hibernate处理并发更新

使用起来相对比较简单,只需做简单的配置,jdo/hibernate就可自动产生对应的sql语句。

用jdo处理并发更新
<version strategy="version-number" column="XXX"/>

<version strategy="timestamp" column="XXX"/>

<version strategy="stateimage" column="XXX"/>

hibernate 处理并发更新
   使用乐观锁
   <version name="version" clumn="XXX"/>
   使用<class>元素的乐观锁属性来定义hibernate采用字段比较来实现乐观锁检查。
   <class dynamic-update="true" optimistic-lock="dirty"...>
   使用悲观锁
   通过调用session.load() session.lock和query.setLockMode()这些方法,锁住对象对应的数据表里的记录。


从数据并发失败中恢复
当并发更新失败,在表示层打印出来不是明智之举。我们应该想办法重新提交事务。一种方式是使用try catch块,不过这种方式将导致业务逻辑代码中含有大量的try catch块。另一种方式是使用Aop interceptor重试事务

注:当应用程序调用了非事务性的api或做了不能恢复的事情(比如发了邮件),事务就不能自动回滚和恢复。在这些情况下,必须在应用程序级别写代码解决。


小结:
如果你使用的是jdbc和ibatis,你必须自己实现乐观锁和悲观锁。相比之下,jdo和hibernate内置的乐观锁和悲观锁会自动产生对应的sql语句。你可以通过一个类的O/R映射来启动乐观锁。关于悲观锁,
jdo需要设置PersistenceManagerFactory属性,hibernate需要在装载对象或执行查询的时候调用一个方法。