【问题标题】:Correct way to handle deadlocks in Hibernate在 Hibernate 中处理死锁的正确方法
【发布时间】:2015-12-31 19:48:12
【问题描述】:

正如这里所说的http://dev.mysql.com/doc/refman/5.0/en/innodb-deadlocks.html

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

这里也注明https://stackoverflow.com/a/2596101/922584

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

在我看来,从 Hibernate 文档来看,它还没有准备好以任何方式处理死锁。在我看来,这些事务以 TransactionRollbackException 结束,然后就完成了。准确的说,我使用的是@Transactional注解。

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

【问题讨论】:

  • 我不明白为什么人们在没有评论的情况下投反对票。
  • 它已经为处理它们做好了充分的准备:它抛出一个异常指示问题,可以由上游代码处理。

标签: mysql hibernate transactions


【解决方案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之外捕获,这样所有的

【讨论】:

  • 感谢您的解释。至于 Hibernate:当使用 @Transactional 注释时,它有助于创建可读性好的代码,并省略了为创建事务编写重复代码。我可以想象这用于重新启动事务的部分可能是此注释的一部分。可行吗?令我惊讶的是,这通常没有被提及或讨论,即使是非常简单的事务,我也经常遇到这些死锁。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-15
    • 2017-03-26
    相关资源
    最近更新 更多