环境:
我们有一个部署在 JBoss 4.2.3.GA 服务器中的应用程序,它使用 Hibernate 3.4 和 JTA 1.0。
有一个导入器创建或更新某些实体,然后导入一些数据。由于多种原因,大部分导入是在新事务中完成的,并且在每个事务中,在外部事务中创建/更新的实体可能会再次更新。
调用序列看起来像这样的伪代码:
服务1:
//container managed transaction T1 is started here
import() {
A a = ... ;//new or read from database
if( isNew( a ) ) {
create(a);
} else {
update(a);
}
Service1 s = ...; //injected or looked up
for( D d : someDataList ) {
//nested transaction T2 is started due to this call, T1 should be suspended
s1.importData(d);
//nested transaction T2 should have been committed here
}
服务2:
@TransactionAttribute(REQUIRES_NEW)
importData(D d) {
A a = ...; //get the corresponding A instance and update as needed
update(a);
//other stuff such as importing d
}
问题:
现在的问题是,我们最终会遇到多个事务试图锁定同一个表的竞争状况,但到目前为止,我们既无法重现问题,也无法确定真正的原因。
不过,我们有一些假设:
由于在 T1 期间更新了一些实体,因此事务获取了一些数据库锁。然后 T1 被挂起,因为 T2 已启动,而 T2 又尝试获取相同的数据库锁并因此被阻塞。T2 最终超时,然后 T1 可以正常完成并释放锁。
可能的解决方案?:
到目前为止,似乎只有一种可能的解决方案:将 T1 中的所有更新包装到另一个事务 T1* 中(可能完全跳过 T1)并让 T1* 和 T2 顺序运行。
如果业务案例允许这样做(我不确定,因为我自己没有实施该业务案例),这是否是一个理智的解决方案?
可能还有其他解决方案,如果有,请提供一些提示。但是,我对此表示怀疑,因为似乎 T1 必须在 T2 尝试获取锁之前释放锁,因此基本上使 T1 和 T2 顺序运行。
问题
综上所述,出现以下问题:
- 我们的假设是否正确,即 T1 是否持有 T2 也需要的锁并且因为它被挂起而无法释放?
- 我上面描述的解决方案是唯一的方法,还是有其他没有手动事务划分的方法?
感谢您阅读所有这些:)
更新1:
因为我不是代码的编写者,所以我也必须深入研究它。到目前为止,在 Hibernate 中没有任何显式锁定的提示,因此 AFAIK Hibernate 仅在写入数据库期间使用数据库锁。当数据库连接打开时。
我们正在使用自动刷新,因此在某些情况下,T1 可能会在 T2 尝试相同操作之前打开连接,但 T1 无法提交和关闭连接,因为它在 T2 提交之前被挂起。因此,数据库似乎由于 T1 的刷新而获得的锁在 T2 刷新之前也无法释放。
使用手动刷新不是解决方案,因为如果 T2 在 T1 之前提交,我们就会丢失更新,但是对实体的更改是相反的。我知道这是设计中的一个缺陷,我们需要修复它,但我也想确认我们的假设是正确的,以便提供一个合理的修复:)