事务隔离级别的困惑

07-10-31 wjl203
事务隔离级别有read uncommited,read commited,repeatable read,serializable。一般写java应用程序指定事务隔离级别(1)在connection的setTransactionIsolation方法中指定(2)用数据库的默认级别

但是在并发事务中,以下代码用Spring的read commited事务管理级别就会出现问题

    protected void doTransfer(int sourceAccount,   
         int targetAccount, BigDecimal amount) {  
         Account source = getAccountDao().getById(sourceAccount);  
         Account target = getAccountDao().getById(targetAccount);  
       
         if (source.getBalance().compareTo(amount) > 0) {  
             // transfer allowed  
             getAccountDao().updateBalance(sourceAccount, amount.negate());  
             getAccountDao().updateBalance(targetAccount, amount);  
   
         } else {  
             throw new RuntimeException("Not enough money");  
         }  
     }  

<p>

如果用read commited,一个事务在source.getBalance().compareTo(amount) > 0判断成功后,执行getAccountDao().updateBalance()前, 另一个并发事务修改了source的balance使balance<amount, 那么就会出现不可重复读问题,doTransfer方法也就出错了.

问题1: 用Spring的事务管理, 是应该用repeatable read 还是 serializable 作为这个方法的隔离级别?

问题2:在hibernate中,像这种问题如何处理?

1)是用悲观锁:

Account source = (Account) session.load(Account.class, accountId,LockMode.UPGRADE);  
<p>

2)还是用乐观锁: versioned data

3)悲观锁能达到repeatable read还是serializable隔离级别?

问题3:如果用乐观锁, 出错后,一般是提示用户数据修改重新处理, 还是在try-catch{}语句中重新执行doTransfer()方法,重新再做一次source.getBalance().compareTo (amount) > 0判断.毕竟这种问题只有在事务并发时才会碰到,可能第二次执行就成功了.

banq
2007-11-01 11:03
首先确认你是什么数据库 不同数据库默认事务隔离级别不一样,只有Oracle默认是read commited。

这种情况是典型的select for update,用乐观锁性能好,悲观锁性能差,使用乐观锁在Hibernate中可以和版本校验结合在一起。

>出错后,一般是提示用户数据修改重新处理, 还是在try-catch{}语句中重新执行doTransfer()方法

需要由用户重新处理,需要重新将新数据推给他,由他确定业务操作。

另外repeatable read还是serializable这两者隔离方式也是性能最低的,基本都是通过记录锁或表锁实现,一般不推荐使用。

[该贴被banq于2007-11-01 11:07修改过]


wjl203
2007-11-01 21:01
感谢大banq的解答,平时只关注了事务的原子性,忽略了事务隔离,到测试阶段并发运行,问题就出现了

猜你喜欢