40

我的应用程序(java spring-core)有几个线程同时运行并访问数据库,我在某些高峰时间遇到异常

07:43:33,400 WARN  [org.hibernate.util.JDBCExceptionReporter] SQL Error: 1213, SQLState: 40001
07:43:33,808 ERROR [org.hibernate.util.JDBCExceptionReporter] Deadlock found when trying to get lock; try restarting transaction
07:43:33,808 ERROR [org.hibernate.event.def.AbstractFlushingEventListener] Could not synchronize database state with session
org.hibernate.exception.LockAcquisitionException: could not insert: [com.xminds.bestfriend.frontend.model.Question]
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:107)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2436)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2856)
    at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:79)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:184)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
    at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:656)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
    at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:147)
    at com.xminds.bestfriend.consumers.Base.onMessage(Base.java:96)
    at org.springframework.jms.listener.adapter.MessageListenerAdapter.onMessage(MessageListenerAdapter.java:339)
    at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:535)
    at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:495)
    at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:467)
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:325)
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:263)
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1058)
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1050)
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:947)
    at java.lang.Thread.run(Thread.java:662)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
    at com.mysql.jdbc.Util.getInstance(Util.java:386)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1065)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4074)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4006)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2468)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2629)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2719)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2155)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2450)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2371)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2355)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
    at org.hibernate.jdbc.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:46)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2416)
    ... 25 more

我的代码看起来

try
{
       this.consumerTransactionTemplate.execute(new TransactionCallbackWithoutResult(){

                    @Override
                    protected void doInTransactionWithoutResult(
                            TransactionStatus status)
                    {
                        process();
                    }

                });

  }
  catch(Exception e){
     logger.error("Exception occured " , e);
      //TODO: Exception handling
  }
4

6 回答 6

41

MySQL 的 InnoDB 引擎支持行级锁定,即使您的代码插入或更新单行(特别是在正在更新的表上有多个索引时),这也可能导致死锁。最好的办法是围绕此设计代码,以便在事务由于死锁而失败时重试事务。此处提供了有关 MySQL 死锁诊断和可能的解决方法的一些有用信息。

在 Spring 中通过 AOP 进行死锁重试的有趣实现可以在此处获得。这样,您只需将注解添加到您想要在死锁情况下重试的方法中。

于 2013-07-19T14:35:56.703 回答
25

埃米尔的回答很棒,它描述了您遇到的问题。不过我建议你试试spring-retry

这是一个通过注解实现重试模式的出色框架。

例子:

 @Retryable(maxAttempts = 4, backoff = @Backoff(delay = 500))
 public void doSomethingWithMysql() {
   consumerTransactionTemplate.execute(
             new TransactionCallbackWithoutResult(){
                @Override
                protected void doInTransactionWithoutResult(                 
                      TransactionStatus status)
                {
                    process();
                }

            });
 } 

如果出现异常,它将重试(调用)方法doSomethingWithMysql( ) 最多 4 次,退避策略为 500ms

于 2015-07-06T13:24:01.203 回答
1

如果您使用的是 JPA/Hibernate,那么只需按照以下步骤操作即可避免死锁。一旦你获得了锁,不要在事务中的任何地方对具有相同 id 的 db 进行任何调用(我的意思是说你不应该在相同的id上再次获取实体),在锁定对象上你修改并保存没有问题。

服务水平:-

employee=empDao.getForUpdate(id);

道级:-

public employee getForUpdate(String id)
return mySqlRepository.getForUpdate(id)

存储库(接口):-

@Lock(LockModeType.PESSIMITSIC_WRITE)
@Query("select e from employee e where id=?1")
public employee getForUpdate(String id)
于 2020-01-11T07:38:28.520 回答
1

当您遇到这种错误“检测到死锁”时。您应该检查您的查询执行情况并验证两个或更多并发事务是否会导致死锁。

这些事务应该以相同的顺序获取数据库锁以避免死锁。

于 2017-02-07T07:39:04.200 回答
1

这是一个简单的 Spring 示例,没有额外的框架。

    TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); // autowired
    transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); // this increases deadlocks, but prevents multiple simultaneous similar requests from inserting multiple rows
    Object o = transactionTemplate.execute(txStatus -> {
            for (int i=0; i<3; i++) {
                try {
                    return findExistingOrCreate(...);
                } catch (DeadlockLoserDataAccessException e) {
                    Logger.info(TAG, "create()", "Deadlock exception when trying to find or create. Retrying "+(2-i)+" more times...");
                    try { Thread.sleep(2^i*1000); } catch (InterruptedException e2) {}
                }
            }
            return null;
    });
    if (o == null) throw new ApiException(HttpStatus.SERVICE_UNAVAILABLE, "Possible deadlock or busy database, please try again later.");

使用可序列化事务隔离级别特定于我的情况,因为它转换SELECTSELECT ... IN SHARE MODE/SELECT ... FOR UPDATE并锁定这些行。它findExistingOrCreate()正在对现有行进行大量复杂的搜索,自动生成名称并检查坏词等。当许多相同的请求同时进入时,它会创建多行。有了可序列化的事务隔离级别,它现在是幂等的;它现在锁定行,创建单行,并且所有后续请求都返回新的现有行。

于 2019-12-17T05:56:13.373 回答
0

这也可能发生在一个线程连续插入记录的非并发应用程序上。如果表具有唯一约束,MySQL 在提交后“构建”该约束。这会锁定表并可能会干扰导致上述死锁的下一次插入。虽然我只在 Windows 上注意到了这个错误。

像所有其他答案一样,重复插入解决了这个问题。

对于其他数据库——PostgreSQL、Oracle 或 H2——它可以在没有这种解决方法的情况下工作。

于 2021-10-20T20:06:15.947 回答