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

时间:2015-12-31 19:48:12

标签: mysql hibernate transactions

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

  

...通常,您必须编写应用程序,以便在因死锁而回滚时,它们始终准备重新发出事务。

此处还注明https://stackoverflow.com/a/2596101/922584

  

如果您正在使用InnoDB或任何行级事务RDBMS,那么即使在完全正常的情况下,任何写入事务都可能导致死锁。

从Hibernate文档中我觉得它没有准备好以无路径处理死锁。在我看来,这些事务以TransactionRollbackException结束,然后就完成了。确切地说,我正在使用@Transactional注释。

如果这是真的,所有这些关键系统将永远无法使用Hibernate。那些银行/移动运营商系统如何应对这些?

2 个答案:

答案 0 :(得分:1)

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

另外我认为他们不会将InnoDB用于任何严重的代码,因为它甚至会在页锁上死锁。较大的商业数据库(如Oracle和MS SQL Server)使用页锁和行锁以更精细的方式处理此问题。例如,通过控制页面填充和空百分比可以更好地控制Oracle,这可以减少阻止事务的页锁的机会(不会死锁,只会阻塞一段时间)。

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

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

InnoDB甚至单记录事务死锁时具有高事务量。这不应该发生,并且是MySQL(和衍生物问题)。如果您正在解决这个技术问题,那就是乐透:您可以通过更改ACID合规性或更改技术/逻辑表布局(例如通过分区)来增加机会。分区将减少DBMS更新索引所花费的时间,从而缩短事务时间,从而减少因记录锁升级而导致死锁的可能性。将页面大小减小到较小的页面大小具有类似的效果。但是:它只是减少了机会(所以你看到更少的死锁,但它们仍然会发生)。

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

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

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

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

  

功能A调用功能B,C,D但是B,C,D除了确认或密钥(如果可能)之外不会向A返回任何数据。

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

  

有些用户通过操作调用控制器。此操作调用函数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之外捕获,以便所有

答案 1 :(得分:1)

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