Hibernate:尝试获取锁定时发现死锁

我在我的项目中使用hibernate,我得到随机的表观死锁,用于非常简单的数据库操作。

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

问题经常出现在类似的条款中:

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

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

我的Hibernate配置:

  ${database.dialect} ${database.structure} ${database.connection} ${database.username} ${database.password} ${database.driver} true 0 UTF-8 UTF-8 ${database.show_sql} false disabled  org.hibernate.connection.C3P0ConnectionProvider 0 50 120 0 0 0 120 1 8 

EDIT1:

  • 我上面写的是发生了表观死锁 – 这是错误的,只有“试图获取锁定时发现死锁”发生。

EDIT2:

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

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

因为死锁经常发生,所以看起来应用程序的某些线程长时间持有锁。

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

如果一个线程长时间持有锁并以某种顺序获取它们,并且第二个线程以不同的顺序获取相同的锁,则必然会发生死锁(有关此频繁死锁原因的详细信息,请参见此处) )。

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

要解决此问题,请尝试在项目中搜索Isolation.REPEATABLE_READIsolation.SERIALIZABLE用法,以查看是否正在使用它。

作为替代方法,使用默认的READ_COMMITTED隔离级别并使用@Version注释实体,以使用乐观锁定来处理并发。

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

这是一个log4j配置,用于记录实体管理器和事务begin / commit / rollback的创建/删除:

           
  1. 我可以以某种方式执行更新查询(JPA / Native)而无需通过@Transactional锁定表吗?

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

  1. 我可以不使用@Transactional进入会话吗? 例如,如果该方法未使用@Transactional注释,则调度线程会尝试将实体的Lazy字段读取为LazyInitializationException – 无会话

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

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

这是MySQL的错误。

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

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

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

在两个连接同时执行的情况下,连接1将获得密钥(1)上的锁定,以及密钥(2)上的连接2。 之后,两个连接都将等待其他连接释放锁上的锁。 这导致死锁。

但是,按交易顺序稍微调整一下,就可以避免死锁。

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

以上重新订购是死锁certificate。

避免死锁的其他方法是使用事务管理机制。 Spring的事务管理几乎是即插即用的。 此外,您可以实施死锁重试策略。 通过Spring AOP进行有趣的死锁重试可以在这里找到。 这样,您只需要将注释添加到要在死锁情况下重试的方法。

有关死锁的更多调试日志以找出可疑的语句,请尝试运行“show engine innodb status”诊断程序。 此外,您还可以查看如何应对死锁

更新:事务DB操作中死锁的方案。

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

Shishir