我在函数中使用Java(Servlet)中的以下代码(用@Transactional(rollbackFor = Exception.class)
包裹:
ModelAccount account = hibernateSession.get(**Class<T> type**, **int id**, new LockOptions(LockMode.PESSIMISTIC_WRITE));
当我通过2个不同的线程同时运行此函数时,我注意到通过此行的第一个线程获取(例如,account.balance = 100)。第二个线程挂起,直到第一个提交。在我的例子中,第一个线程是将余额更新为200。 当锁定的线程恢复时,它仍然会读取account.balance = 100而不是新值。
我在mysql上的隔离级别:
SHOW GLOBAL VARIABLES LIKE 'tx_isolation';
Variable_name Value
tx_isolation REPEATABLE-READ
修改
如果我手动锁定帐户的行,即使隔离级别在数据库级别和休眠级别@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
最后一件事,当执行运行时,我在日志中注意到以下内容:
Hibernate: select id from accounts where id =? for update
这清楚地表明hibernate没有获取帐户的全部细节,而是在使用他之前拥有的数据时发出锁定它的命令。
答案 0 :(得分:2)
从技术上讲,这不是脏数据。脏读只能在READ_UNCOMMITTED上进行,所以你在这里看到的是可重复的读取,而你想要的是不可重复的读取。
PESSIMISTIC_WRITE对所选数据库行进行独占锁定,因此在第一个事务结束(提交或回滚)之前,没有其他线程可以选择此记录。
第二个交易看到100
的值的原因是REPEATABLE_READ isolation level。 InnoDB使用MVCC,在REPEATABLE_READ下,它保证您可以看到记录,就像交易开始时一样。
如果要读取最新值,则需要切换到READ_COMMITTED。
但即使在READ_COMMITTED中,如果Hibernate已经在第一级缓存中缓存了该实体,您仍可能获得100
的值。 Hibernate提供application-level repeatable reads,因此一旦加载实体,无论您尝试加载多少次实体引用,都会得到相同的实体引用。
确保@Transactional
isolation level is taken into consideration。如果您使用的是JTA,则可能会被忽略。
如果Hibernate以前加载了实体,除非你发出refresh,否则它不会重新加载它。
ModelAccount account = hibernateSession.refresh(ModelAccount.class, id, new LockOptions(LockMode.PESSIMISTIC_WRITE));
答案 1 :(得分:0)
如果您使用的是@Transactional
注释,请确保您拥有正确的传播策略。请查看文档中的第16.5.7节:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html
您可以通过指定REQUIRES_NEW传播策略来解决此问题。