Hibernate如何决定更新/插入/删除的顺序?

40

首先,让我们暂时不考虑Hibernate。假设我有两个表A & B。两个交易正在更新这两个表中的相同记录,但txn1先更新B再更新A,而txn2则先更新A再更新B。这是典型的死锁示例。避免死锁的最常见方法是预先定义获取资源的顺序。例如,我们应该先更新表A,然后再更新B。

回到Hibernate。当我们在一个会话中更新大量实体时,一旦我刷新会话,不同实体的更改将生成相应的插入/更新/删除语句到数据库中。Hibernate是否有一些算法来决定实体之间更新的顺序?如果没有,Hibernate用什么方式来防止第1段描述的死锁情况?

如果Hibernate正在维护顺序,那么我怎样才能知道或控制顺序?我不希望我的显式更新与Hibernate冲突,并导致死锁。

2个回答

63
您描述的问题不是由数据库处理的,根据我的经验,Hibernate也没有完全处理这个问题。你需要采取明确的步骤以避免它成为一个问题。Hibernate为您做了部分工作。正如之前的回答所述,Hibernate确保在独立刷新中,插入、删除和更新按一定顺序排序,以确保它们将按可实现的顺序应用。请参见AbstractFlushingEventListener类中的performExecutions(EventSource session)
执行所有SQL(和二级缓存更新)以特定的顺序,以便外键约束不会被违反:
1.按执行顺序插入 2.更新 3.集合元素的删除 4.集合元素的插入 5.按执行顺序删除
当存在唯一约束时,了解这个顺序非常重要,特别是如果你想替换一个一对多的子项(删除旧的/插入新的),但旧的和新的子项都共享相同的唯一约束(例如相同的电子邮件地址)。在这种情况下,您可以更新旧条目,而不是删除/插入,或者您可以仅在删除后刷新,然后继续插入。有关更详细的示例,请查看此文章
请注意,它没有指定更新的顺序。检查Hibernate代码让我认为更新顺序将取决于将实体添加到持久性上下文中的顺序,而不是它们被更新的顺序。这可能在您的代码中是可预测的,但阅读Hibernate代码并不能让我感到我可以依赖那个排序。
我可以想到三个解决方案:
  1. 尝试将hibernate.order_updates设置为true。这可以帮助避免在更新同一表中的多行时发生死锁,但不能解决跨多个表的死锁问题。
  2. 在进行任何更新之前,使您的事务对一个实体使用PESSIMISTIC_WRITE锁定。您使用的实体取决于您的具体情况,但只要确保在存在死锁风险时一致选择一个实体,这将阻塞其余事务直到获得锁。
  3. 编写代码以在发生死锁时捕获并以明智的方式重试。管理死锁重试的组件必须位于当前事务边界之外。这是因为失败的会话必须关闭并回滚相关的事务。在this article中,您可以找到一个自动重试的AOP Aspect示例。

2
非常抱歉,由于未知原因,在此之前我错过了这个答案 :) 我相信您对Hibernate代码的检查结果实际上是迄今为止最可靠的答案。很好的建议,特别是对于第1点和第2点。 - Adrian Shum

0
关于第一个例子 - 这种情况由数据库处理(请了解您的数据库的事务隔离级别和锁定策略)。有许多不同的处理方式。
至于Hibernate,javadoc对org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(EventSource)说:
按照特定顺序执行所有SQL和二级缓存更新,以避免违反外键约束:
  1. 插入,按执行顺序
  2. 更新集合元素的删除
  3. 插入集合元素
  4. 删除,按执行顺序
我认为这是Hibernate执行的唯一优化。其余的问题由数据库处理。

我相信第一个示例的东西大部分都无法由数据库处理。对于隔离级别,从我的理解来看,它更多的是关于一个事务的更改如何对其他事务可见。然而,即使是最低隔离级别,两个事务写入相同的行仍然会有锁定。因此,如果一个事务更新A记录,然后B记录,另一个事务更新B记录,然后A记录,则仍将创建死锁。DBMS可以做的最好的事情就是检测到潜在的死锁情况。我了解你提到的Hibernate SQL执行顺序,但我担心的是关于 - Adrian Shum
实体之间的更新顺序。如果Hibernate没有处理它并假设可以由数据库处理,那将是非常令人惊讶的(显然是不现实的)。 - Adrian Shum
例如,Oracle以某种方式自动处理死锁,“通过回滚检测到死锁的事务关联的语句来自动检测和解决死锁”:http://www.oracle-base.com/articles/misc/deadlocks.php。这可能会导致JDBC异常,因此您可以在处理异常的代码中添加一些重试逻辑。 - Potejciak
当然,更好的方法是通过确保以适当的顺序获取资源来避免死锁。 - Potejciak
这就是我所说的“DBMS...检测潜在死锁发生”的意思。然而,这并不是所有DBMS都广泛提供的功能,应该只作为最后的手段使用。 - Adrian Shum
我曾经遇到过同样的问题,在一个事务中,我对同一实体(关系实体)进行了删除和插入操作,结果出现了相同的行为。为了解决这个问题,我在删除后刷新或将两个查询放在单独的事务中执行。 - Yassine CHABLI

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接