事务隔离级别的困惑

事务隔离级别有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");
}
}


如果用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);

2)还是用乐观锁: versioned data
3)悲观锁能达到repeatable read还是serializable隔离级别?

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

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

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

>出错后,一般是提示用户数据修改重新处理, 还是在try-catch{}语句中重新执行doTransfer()方法
需要由用户重新处理,需要重新将新数据推给他,由他确定业务操作。

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



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


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