我在我的项目中使用了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配置:
<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>
EDIT1:
EDIT2:
这些也发生在这些方法上 - 那些需要用@Transactional注释的内容:
@Transactional
public void setLastActivity() {
em.insertNative("table")
.values(...)
.execute();
}
答案 0 :(得分:22)
因为死锁频繁发生,所以看起来应用程序的某些线程会持续锁定一段时间。
应用程序中的每个线程在访问数据库时都会使用它自己的数据库连接/连接,因此从数据库的角度来看,两个线程是两个不同的客户端,它们竞争数据库锁。
如果一个线程长时间持有锁并按特定顺序获取它们,并且第二个线程同时获取相同的锁但是处于不同的顺序,则必然会发生死锁(请参阅here有关此频繁死锁原因的详细信息)。
在读取操作中也会发生死锁,这意味着某些线程也在获取读锁定。如果线程正在REPEATABLE_READ
隔离级别或SERIALIZABLE
运行事务,则会发生这种情况。
要解决此问题,请尝试在项目中搜索Isolation.REPEATABLE_READ
和Isolation.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 >
可以通过原生查询或JPQL更新查询。
在没有@Transactional
的方法中,查询将在它自己的实体管理器中执行,并且只返回分离的实体,因为在运行查询后会立即关闭会话。
所以没有@Transactional
的方法中的延迟初始化异常是正常的。您也可以将它们设置为@Transactional(readOnly=true)
。
答案 1 :(得分:3)
这是MySQL的错误。
最简单的解决方法&amp;避免死锁是重新排序应用程序中发生的数据库操作。
当多个资源/连接尝试以相反的顺序获取多个锁时,主要发生死锁,如下所示:
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);
以上重新订购是死锁证明。
避免死锁的其他方法是使用事务管理机制。 Spring的Transaction management几乎是即插即用的。此外,您可以实施死锁重试策略。通过Spring AOP进行有趣的死锁重试here。这样,您只需要将注释添加到要在死锁情况下重试的方法。
有关死锁的更多调试日志以找出可疑的语句,请尝试运行“show engine innodb status”诊断。另外,您可以查看 How to Cope with Deadlocks 。
更新:事务数据库操作中死锁的方案。
在事务数据库中,当两个进程在其自己的事务中更新两行信息但顺序相反时,就会发生死锁。例如,进程A在确切的时间帧进程B中更新第1行然后第2行更新第2行然后第1行。进程A无法完成更新第2行直到进程B完成,但是它无法完成更新第1行直到进程完成。无论允许多长时间通过,这种情况永远不会自行解决,因为这个数据库管理系统通常会终止进行最少量工作的进程的事务。
Shishir