一次数据库死锁原因分析

线上crm导出一份报表时长时没反应,日志一看:

 1 2017-09-25 13:45:58 [http-nio-8080-exec-60] ERROR: jdbc.sqltiming#exceptionOccured : 1469. PreparedStatement.executeBatch() FAILED! batching 3 statements: 1: update uwc_applyloan_attach set ATTACH_ID=112103 where ID=44602 2: 
 2 update uwc_applyloan_attach set ATTACH_ID=112106 where ID=44603 3: update uwc_applyloan_attach 
 3 set ATTACH_ID=112109 where ID=44604 
 4  {FAILED after 135899 msec}
 5 java.sql.BatchUpdateException: Lock wait timeout exceeded; try restarting transaction
 6     at com.mysql.jdbc.SQLError.createBatchUpdateException(SQLError.java:1167)
 7     at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:1778)
 8     at com.mysql.jdbc.PreparedStatement.executeBatchInternal(PreparedStatement.java:1262)
 9     at com.mysql.jdbc.StatementImpl.executeBatch(StatementImpl.java:970)
10     at net.sf.log4jdbc.StatementSpy.executeBatch(StatementSpy.java:526)
11     at sun.reflect.GeneratedMethodAccessor387.invoke(Unknown Source)
12     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
13     at java.lang.reflect.Method.invoke(Method.java:601)
14     at org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:114)
15     at com.sun.proxy.$Proxy33.executeBatch(Unknown Source)
16     at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
17     at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
18     at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:268)
19     at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185)
20     at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
21     at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:64)
22     at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:1185)
23     at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1261)
24     at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
25     at com.upg.ucars.framework.base.AbstractHibernateDAO$1.doInHibernate(AbstractHibernateDAO.java:172)
26     at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:406)
27     at org.springframework.orm.hibernate3.HibernateTemplate.executeFind(HibernateTemplate.java:343)
28     at com.upg.ucars.framework.base.AbstractHibernateDAO.queryByParam(AbstractHibernateDAO.java:147)
29     at com.upg.ucars.framework.base.AbstractHibernateDAO.queryByParam(AbstractHibernateDAO.java:92)
30     at com.upg.tenant.yuntao.core.UwcApplyloanAttachDaoImpl.getByApplyId(UwcApplyloanAttachDaoImpl.java:19)
31     at com.upg.es.seal.core.EsSealServiceImpl.generateDownloadData(EsSealServiceImpl.java:568)
32     at com.upg.es.seal.core.EsSealServiceImpl$$FastClassByCGLIB$$f4dc2f5b.invoke(<generated>)
33     at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
34     at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:689)
35     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
36     at org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.invoke(ThrowsAdviceInterceptor.java:124)
37     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
38     at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
39     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
40     at org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.invoke(ThrowsAdviceInterceptor.java:124)
41     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
42     at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
43     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
44     at org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.invoke(ThrowsAdviceInterceptor.java:124)
45     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
46     at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
47     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
48     at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
49     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
50     at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
51     at com.upg.es.seal.core.EsSealServiceImpl$$EnhancerByCGLIB$$1d4e14b1.generateDownloadData(<generated>)
UwcApplyloanAttachDaoImpl.getByApplyId(UwcApplyloanAttachDaoImpl.java:19)这句造成了锁超时,但奇怪的是这个是个查询语句。
分析了下Hibernate源码,原来在查询中还需要先Flush一下,看org.hibernate.impl.SessionImpl源码:

public
List list(String query, QueryParameters queryParameters) throws HibernateException { errorIfClosed(); checkTransactionSynchStatus(); queryParameters.validateParameters(); HQLQueryPlan plan = getHQLQueryPlan( query, false ); autoFlushIfRequired( plan.getQuerySpaces() ); List results = CollectionHelper.EMPTY_LIST; boolean success = false; dontFlushFromFind++; //stops flush being called multiple times if this method is recursively called try { results = plan.performList( queryParameters, this ); success = true; } finally { dontFlushFromFind--; afterOperation(success); } return results; }



    /**
     * detect in-memory changes, determine if the changes are to tables
     * named in the query and, if so, complete execution the flush
     */
    protected boolean autoFlushIfRequired(Set querySpaces) throws HibernateException {
        errorIfClosed();
        if ( ! isTransactionInProgress() ) {
            // do not auto-flush while outside a transaction
            return false;
        }
        AutoFlushEvent event = new AutoFlushEvent(querySpaces, this);
        AutoFlushEventListener[] autoFlushEventListener = listeners.getAutoFlushEventListeners();
        for ( int i = 0; i < autoFlushEventListener.length; i++ ) {
            autoFlushEventListener[i].onAutoFlush(event);
        }
        return event.isFlushRequired();
    }

org.hibernate.event.def.AbstractFlushingEventListener

    protected void performExecutions(EventSource session) throws HibernateException {

        log.trace("executing flush");

        try {
            session.getJDBCContext().getConnectionManager().flushBeginning();
            // we need to lock the collection caches before
            // executing entity inserts/updates in order to
            // account for bidi associations
            session.getActionQueue().prepareActions();
            session.getActionQueue().executeActions();
        }
        finally {
            session.getJDBCContext().getConnectionManager().flushEnding();
        }
    }

org.hibernate.jdbc.AbstractBatcher

    public void executeBatch() throws HibernateException {
        if (batchUpdate!=null) {
            try {
                try {
                    doExecuteBatch(batchUpdate);
                }
                finally {
                    closeStatement(batchUpdate);
                }
            }
            catch (SQLException sqle) {
                throw JDBCExceptionHelper.convert(
                        factory.getSQLExceptionConverter(),
                        sqle,
                        "Could not execute JDBC batch update",
                        batchUpdateSQL
                    );
            }
            finally {
                batchUpdate=null;
                batchUpdateSQL=null;
            }
        }
    }

看到这里就明白了, doExecuteBatch(batchUpdate); 如果有待Flush的update操作存在,前面一次操作的长事务还没结束而同时另外一个长事务操作又开始了,到了getByApplyId这里又去更新同样的记录就得等待锁释放了

 

 

 

喜欢艺术的码农
原文地址:https://www.cnblogs.com/zjhgx/p/7773202.html