在 Hibernate 中处理死锁的正确方法

Correct way to handle deadlocks in Hibernate

正如此处所述http://dev.mysql.com/doc/refman/5.0/en/innodb-deadlocks.html:

... Normally, you must write your applications so that they are always prepared to re-issue a transaction if it gets rolled back because of a deadlock.

这里也注明了:

If you are using InnoDB or any row-level transactional RDBMS, then it is possible that any write transaction can cause a deadlock, even in perfectly normal situations.

从 Hibernate 文档来看,在我看来它不准备以任何方式处理死锁。在我看来,这些交易以 TransactionRollbackException 结束,然后就完成了。准确地说,我使用的是 @Transactional 注释。

如果这是真的,那么所有那些关键系统将永远无法使用 Hibernate。所有这些 bank/mobile 运营商系统如何处理这些?

通过将几乎所有内容都包装在事务中并注意创建的代码没有死锁可能的代码(真正理解数据模型,写出流程),它们可以防止死锁。 InnoDB 使用当前的页面锁定方式很容易死锁。

我还认为他们不会将 InnoDB 用于任何严肃的代码,因为这甚至会导致页面锁死锁。 Oracle 和 MS SQL Server 等较大的商业数据库以更细粒度的方式使用页锁和行锁来处理此问题。此外,例如可以通过控制页面填充和空百分比来更好地控制 Oracle,这减少了页面锁定阻塞事务的机会(不是死锁,只是阻塞了一会儿)。

至于 hibernate 或任何其他代码:一旦 运行 陷入死锁情况,代码通常需要改进。这个想法是你可以控制重新启动事务(在某些情况下给你一个死锁循环:死锁,重新启动和再次死锁等)。这种代码可能很简单:

  • 发生死锁异常;
  • 只在初始调用函数中捕获死锁异常;
  • 使用相同的参数再次调用初始调用函数。

具有高事务量的 InnoDB 即使是单条记录事务也会死锁。这不应该发生并且是 MySQL(和衍生品问题)。如果您正在解决这个技术问题,那就是彩票:您可以通过更改 ACID 合规性或通过更改 technical/logical table 布局(例如通过分区)来增加您的机会。分区将减少 DBMS 更新索引所花费的时间,从而缩短事务时间,从而减少由于记录锁升级而造成死锁的机会。将页面大小减小到更小的页面大小具有类似的效果。然而:它只是减少了机会(所以你看到更少的死锁,但它们仍然会发生)。

有两种可能的解决方案可以完全避免死锁:

  • 重写代码,使单例处理发生死锁的table。必须对代码进行分区,以便在此 table 上执行操作的代码位于单个事务中,而所有其他代码位于其他事务中。这会破坏任何事务设计,如果出现另一个问题,回滚事务(现在至少有 2 个甚至可能是 3 个事务)是一场噩梦。
  • 切换 DBMS 引擎:如果您使用 hibernate,这非常简单,只是您必须学习处理这个新 DBMS 的内部结构。

处理死锁 hibernate/Spring/JPA @Transaction:

使用单例设计,最好是单向流:

Function A calls Function B,C,D however B,C,D are not returning any data to A except a confirmation or a key (if possible).

Hibernate 在异常情况下回滚缓存中的更改,因此主要担心的是调用函数。只要是无状态的,最重要的是事务启动函数A:

Some user calls the controller with action. This action calls function A:

public String someControllerAction(...) {
  try {
     ... some work, should be minimal else we have to undo this in the catch
     saveMyData(...);
  } catch(TransactionException exp) {
     ... undo some work
/* This is a loop, so it can get stuck. You can keep a loop 
counter or another check to prevent getting stuck forever. 
You can also throw this back to the user with a **please retry 
button** */
     someControllerAction(...); 
  }

// Starting transaction so that it can be restarted.
@Transactional
private saveMyData(...) throws TransactionException {
   try {
     ... some work
   } catch(TransactionException exp) {
     ... some roll back work if required
     Throw new TransactionException();
   }
}

数据访问层抛出的异常必须在@Transaction之外捕获,这样所有

我公司在高事务高并发系统中大量使用了Hibernate,可以说它不仅没有处理,而且没有办法编写排除死锁的代码。任何严重的复杂软件系统都会发生死锁。无论数据库引擎还是其他东西。您当然可以减少机会,但它仍然会发生并且您的代码必须准备就绪。在 Hibernate 中重新启动 txn 的最大问题是发生死锁后,POJO 最终处于某种不确定的脏状态(一些 POJO 被保存到 DB,其他的 - 没有)。为了重试 txn,您必须从数据库中重建所有对象,这会使代码变得非常复杂——基本上这意味着您不能像使用真正的 POJO 那样使用 POJO。相反,这些对象变成了与 DB 交互所需的一次性特殊用途对象(我认为这违背了 JPA 的原始概念)......所以,长话短说,我们最终修改了 Hibernate 并添加了一个特殊的交易失败后 "reset" 缓存的机制,以便可以重试交易。这允许我们在方法内重试事务(而不是强制数据库事务跨越整个 java 方法调用)。 简而言之,我同意上面的观点,即 Hibernate(或者我猜任何 JPA)不太适合关键任务高性能应用程序。