【问题标题】:Hibernate: Deadlock found when trying to obtain lockHibernate:尝试获取锁时发现死锁
【发布时间】:2014-02-14 10:45:36
【问题描述】:

我在我的项目中使用休眠,但对于非常简单的数据库操作,我遇到了随机的明显死锁。

有一个堆栈跟踪:https://gist.github.com/knyttl/8999006 - 让我感到困惑的是,第一个异常是 RollbackException,然后是 LockAquisition 异常。

问题经常发生在类似的子句上:

@Transactional
public void setLastActivity() {
    User user = em.findById(...);
    user.setLastActivity(new Date());
    em.merge(user);
    em.flush();
}

我很困惑,因为我不知道是 Hibernate、MySQL 还是 C3P0 的问题。

我的休眠配置:

            <prop key="hibernate.dialect">${database.dialect}</prop>
            <prop key="hibernate.hbm2ddl.auto">${database.structure}</prop>
            <prop key="hibernate.connection.url">${database.connection}</prop>
            <prop key="hibernate.connection.username">${database.username}</prop>
            <prop key="hibernate.connection.password">${database.password}</prop>
            <prop key="hibernate.connection.driver_class">${database.driver}</prop>
            <prop key="hibernate.connection.shutdown">true</prop>
            <prop key="hibernate.connection.writedelay">0</prop>
            <prop key="hibernate.connection.characterEncoding">UTF-8</prop>
            <prop key="hibernate.connection.charSet">UTF-8</prop>
            <prop key="hibernate.show_sql">${database.show_sql}</prop>
            <prop key="hibernate.format_sql">false</prop>
            <prop key="hibernate.ejb.metamodel.generation">disabled</prop>
            <!-- Use the C3P0 connection pool provider -->
            <prop key="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</prop>
            <prop key="hibernate.c3p0.min_size">0</prop>
            <prop key="hibernate.c3p0.max_size">50</prop>
            <prop key="hibernate.c3p0.timeout">120</prop>
            <prop key="hibernate.c3p0.max_statements">0</prop>
            <prop key="hibernate.c3p0.max_statementsPerConnection">0</prop>
            <prop key="hibernate.c3p0.maxStatementsPerConnection">0</prop>
            <prop key="hibernate.c3p0.idle_test_period">120</prop>
            <prop key="hibernate.c3p0.acquire_increment">1</prop>
            <prop key="hibernate.c3p0.numHelperThreads">8</prop>

编辑1:

  • 我在上面写道,发生了明显的死锁 - 这是错误的,只有“尝试获取锁时发现死锁”发生。

EDIT2:

这也发生在这些方法上 - 那些需要用 @Transactional 注释的方法:

@Transactional
public void setLastActivity() {
    em.insertNative("table")
           .values(...)
           .execute();
}

【问题讨论】:

  • 您需要提供更多详细信息,例如您使用堆栈跟踪获得的实际异常。我的意思是,不是在某些可能被公司防火墙阻止的外部站点上。
  • 您可能想要设置 autocommit=false。
  • Gimby:给出了堆栈跟踪 - 仔细看。 brettw:你能提供更多信息吗?为什么会有帮助?
  • 当你摆脱@Transactional 时会发生什么?由于 @Transactional 注释,最近与休眠发生了死锁。
  • setLastActivity方法有点奇怪,user是一个附加实体,所以不需要合并,合并对附加实体没有影响。也不需要在方法结束之前刷新,因为 spring 会为我们做刷新。进行手动冲洗有什么特殊原因吗?可能是相关的

标签: java mysql hibernate c3p0


【解决方案1】:

由于死锁如此频繁地发生,看起来应用程序的某些线程持有锁很长时间。

应用程序中的每个线程在访问数据库时都将使用它自己的数据库连接/连接,因此从数据库的角度来看,两个线程是两个不同的客户端,它们竞争数据库锁。

如果一个线程持有锁的时间很长,并以一定的顺序获取它们,然后第二个线程以不同的顺序获取相同的锁,则必然会发生死锁(详见here)关于这个频繁的死锁原因)。

在读操作中也会发生死锁,这意味着一些线程也在获取读锁。如果线程在REPEATABLE_READ 隔离级别或SERIALIZABLE 中运行事务,则会发生这种情况。

为了解决这个问题,请尝试在项目中搜索Isolation.REPEATABLE_READIsolation.SERIALIZABLE的用法,看看是否正在使用。

作为替代方案,使用默认的READ_COMMITTED 隔离级别并使用@Version 注释实体,以改用optimistic locking 处理并发。

还要尝试识别长时间运行的事务,这有时会发生在 @Transactional 放置在错误的位置并包装例如批处理示例中的整个文件的处理时,而不是逐行执行事务.

这是一个 log4j 配置,用于记录实体管理器的创建/删除和事务开始/提交/回滚:

   <!-- spring entity manager and transactions -->
<logger name="org.springframework.orm.jpa" additivity ="false">
    <level value="debug" />
    <appender-ref ref="ConsoleAppender" />
</logger >
<logger name="org.springframework.transaction" additivity ="false">
    <level value="debug" />
    <appender-ref ref="ConsoleAppender" />
</logger >
  1. 我能否以某种方式执行更新查询(JPA/Native)而不必通过@Transactional 锁定表?

可以通过本机查询或JPQL 进行更新查询。

  1. 我可以在不使用@Transactional 的情况下以某种方式进入会话吗? 例如,计划线程尝试读取实体上的延迟字段导致 LazyInitializationException - 如果方法未使用 @Transactional 注释,则没有会话

在没有@Transactional 的方法中,查询将在它自己的实体管理器中执行,并且只返回分离的实体,因为你的会话在查询运行后立即关闭。

所以没有@Transactional的方法中的延迟初始化异常是正常的。您也可以将它们设置为@Transactional(readOnly=true)

【讨论】:

  • 感谢您的回复。我没有找到任何隔离设置:始终使用默认值。对我来说最有趣的是找出哪些事务是导致锁定的那些事务。从例外中我只看到那些无法获得它们的人。有没有办法找到它们?
  • 我刚刚添加了一些 log4j 配置来记录事务开始/提交/回滚,它也会记录隔离和传播。还有用于创建/销毁实体管理器的日志记录。
  • DBA 是否查看过数据库,看看是否有其他东西在上面运行,是死锁的原因吗?他们应该能够提供大量的输入
  • 不幸的是,我是 DBA,所以 :-) 好吧,我会接受你的回答,因为我现在走在正确的轨道上。我有几个小问题要问您: 1. 我能否以某种方式执行更新查询(JPA/Native)而不必通过@Transactional 锁定表? 2. 我可以在不使用@Transactional 的情况下以某种方式进入会话吗?例如,调度线程尝试读取实体上的惰性字段产生 LazyInitializationException - 如果方法未使用 @Transactional 注释,则无会话。
  • 我已经在答案中添加了这两个问题,可以批量更新并且没有@Transactional的方法中的LazyInitializationException是正常的
【解决方案2】:

这是 MySQL 的错误。

解决和避免死锁的最简单方法是重新排序应用程序中发生的数据库操作。

当多个资源/连接尝试以相反的顺序获取多个锁时,通常会发生死锁,如下所示:

connection 1: locks key(1), locks key(2);
connection 2: locks key(2), locks key(1);

在两个连接同时执行的场景中,连接1会在key(1)上获得锁,而连接2会在key(2)上获得锁。之后,两个连接都将等待其他连接释放密钥上的锁。这会导致死锁。

但是,稍微调整一下事务的顺序,就可以避免死锁。

connection 1: locks key(1), locks key(2);
connection 2: locks key(1), locks key(2);

上面的重新排序是死锁证明。

避免死锁的其他方法是拥有事务管理机制。 Spring 的Transaction management 几乎是即插即用的。此外,您可以制定死锁重试策略。通过 Spring AOP 进行的一个有趣的死锁重试可以在 here 找到。这样你只需要在你想重试的方法上添加注解,以防死锁。

如需更多有关死锁的调试日志以找出可疑语句,请尝试运行“show engine innodb status”诊断程序。另外,您可以查看How to Cope with Deadlocks

更新:事务性数据库操作中的死锁场景。

在事务数据库中,当两个进程各自在自己的事务中更新两行信息但顺序相反时,就会发生死锁。例如,进程 A 在确切的时间范围内更新第 1 行然后第 2 行进程 B 更新第 2 行然后第 1 行。进程 A 无法完成更新第 2 行直到进程 B 完成,但它不能完成更新第 1 行直到进程A 完成。无论允许经过多少时间,这种情况永远不会自行解决,因此数据库管理系统通常会终止完成工作量最少的进程的事务。

希希尔

【讨论】:

  • 我理解这个问题,并且我已经阅读了类似的描述。但这发生在我上面提到的非常简单的交易中。我没有手动锁定表,它是由 Hibernate 和 @Transactional 注释完成的。
  • 只是好奇,如果您删除 @Transactional 注释,应用程序是否可以在没有死锁的情况下工作? Hibernate 中 95% 的死锁情况来自无序的 DB 事务,如果将 DDL 和 DML 重新排序为更同步的顺序,则可以避免这种情况。
  • 我认为所有 entitymanager 调用都必须包含在事务中——这就是为什么要对其进行注释。所以没必要?
  • 事务是必要的,但在应用程序中的给定时间点可能有多个事务在运行,如果操作未按正确顺序处理,则可能以死锁告终。我在回答中添加了对事务数据库死锁的另一种解释。
猜你喜欢
  • 1970-01-01
  • 2017-02-07
  • 1970-01-01
  • 2017-04-05
  • 1970-01-01
  • 1970-01-01
  • 2015-11-22
  • 2013-09-20
  • 2013-07-18
相关资源
最近更新 更多