37

让我们首先忘记 Hibernate。假设我有两个表,A & B。两个事务正在更新这两个表中的相同记录,但是 txn 1 更新 B 然后 A,而 txn 2 更新 A 然后 B。这是一个典型的死锁示例。避免这种情况的最常见方法是预先定义获取资源的顺序。例如,我们应该先更新表 A,然后再更新 B。

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

如果 Hibernate 正在维护订单,我如何知道或控制订单?我不希望我在 DB 中的显式更新与 Hibernate 发生冲突,并导致死锁。

4

2 回答 2

55

您描述的问题不是由数据库处理的,根据我的经验,Hibernate 也没有完全处理。

您必须采取明确的步骤来避免它成为问题。

Hibernate 会为您完成一些工作。根据前面的答案,Hibernate 确保在隔离刷新中,插入、删除和更新的排序方式确保它们将以可实现的顺序应用。请参阅AbstractFlushingEventListener 类中的performExecutions(EventSource session)

以特殊顺序执行所有 SQL(和二级缓存更新),以免违反外键约束:

  1. 插入,按执行顺序
  2. 更新
  3. 删除集合元素
  4. 集合元素的插入
  5. 删除,按执行顺序

当有唯一约束时,了解这个顺序非常重要,特别是如果你想替换一个一对多的孩子(删除旧的/插入新的)但是旧的和新的孩子共享相同的唯一约束(例如相同的电子邮件地址)。在这种情况下,您可以更新旧条目,而不是删除/插入,或者您可以在删除后刷新,然后继续插入。有关更详细的示例,您可以查看这篇文章

请注意,它没有指定更新的顺序。检查 Hibernate 代码使我认为更新顺序将取决于将实体添加到持久性上下文中的顺序,而不是更新它们的顺序。这在您的代码中可能是可以预测的,但是阅读 Hibernate 代码并没有让我觉得我会依赖这种排序。

我能想到的解决方案有以下三种:

  1. 尝试将hibernate.order_updates设置为true。当同一个表中的多行被更新时,这应该有助于避免死锁,但对跨多个表的死锁没有帮助。
  2. 在进行任何更新之前,让您的事务对其中一个实体进行PESSIMISTIC_WRITE锁定。您使用哪个实体将取决于您的具体情况,但只要您确保在存在死锁风险的情况下始终选择实体,这将阻塞事务的其余部分,直到可以获得锁为止。
  3. 编写代码以在死锁发生时捕获并以合理的方式重试。管理死锁重试的组件必须位于当前事务边界之外。这是因为必须关闭失败的会话并回滚相关的事务。在本文中,您可以找到一个自动重试 AOP 特性的示例。
于 2014-04-04T11:32:56.830 回答
-1

关于第一个例子 - 这种东西由数据库处理(阅读更多关于事务隔离级别和数据库锁定策略的信息)。有许多不同的方法可以处理这个问题。

至于 Hibernate,org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(EventSource) 的 javadoc 说:

以特殊顺序执行所有 SQL 和二级缓存更新,以免违反外键约束:

  1. 插入,按执行顺序
  2. 更新集合元素的删除
  3. 集合元素的插入
  4. 删除,按执行顺序

我假设这是 Hibernate 执行的 SQL 查询的唯一优化。其余问题由数据库处理。

于 2012-09-27T08:20:48.153 回答