postgresql 并发update下导致的死锁问题
一、死锁问题背景
在收据批量打印时,由于采用异步并发触发打印,同时触发打印(九千多数据 每隔50ms触发一次),导致了并发执行引起在接口更新打印次数时postgresql发生死锁问题,
具体报错如下:
1 ### The error occurred while setting parameters 2 ### SQL: update t_sc_receipt set print_num = coalesce(print_num,0) + 1 ,print_ts=? 3 ### Cause: org.postgresql.util.PSQLException: ERROR: deadlock detected 4 详细:Process 19540 waits for ShareLock on transaction 12520113; blocked by process 19539. 5 Process 19539 waits for ShareLock on transaction 12520112; blocked by process 19540. 6 建议:See server log for query details. 7 在位置:while rechecking updated tuple (329,3) in relation "t_sc_receipt" 8 ; SQL []; ERROR: deadlock detected 9 详细:Process 19540 waits for ShareLock on transaction 12520113; blocked by process 19539. 10 Process 19539 waits for ShareLock on transaction 12520112; blocked by process 19540. 11 建议:See server log for query details. 12 在位置:while rechecking updated tuple (329,3) in relation "t_sc_receipt"; nested exception is org.postgresql.util.PSQLException: ERROR: deadlock detected 13 详细:Process 19540 waits for ShareLock on transaction 12520113; blocked by process 19539. 14 Process 19539 waits for ShareLock on transaction 12520112; blocked by process 19540. 15 建议:See server log for query details. 16 在位置:while rechecking updated tuple (329,3) in relation "t_sc_receipt" 17 at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:263) 18 at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73) 19 at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73) 20 at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:368) 21 at com.sun.proxy.$Proxy57.update(Unknown Source) 22 at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:254) 23 at com.xxx.framework.mybatis.dao.impl.MyBatisDaoImpl.updateBySql(MyBatisDaoImpl.java:531) 24 at com.xxx.dscsettle.receipt.ReceiptService.updatePrintNum(ReceiptService.java:160) 25 at com.xxx.dscsettle.receipt.ReceiptService$$FastClassBySpringCGLIB$$82e91731.invoke(<generated>) 26 at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) 27 at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:651) 28 at com.xxx.dscsettle.receipt.ReceiptService$$EnhancerBySpringCGLIB$$67b6a353.updatePrintNum(<generated>) 29 at com.alibaba.dubbo.common.bytecode.Wrapper72.invokeMethod(Wrapper72.java) 30 at com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:46) 31 at com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:72) 32 at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53) 33 at com.onlyou.framework.log.DubboLogServiceFilter.invoke(DubboLogServiceFilter.java:28) 34 at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) 35 at com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:64) 36 at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) 37 at com.alibaba.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:42) 38 at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) 39 at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75) 40 at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) 41 at com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:78) 42 at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) 43 at com.alibaba.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:61) 44 at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) 45 at com.alibaba.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:132) 46 at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) 47 at com.alibaba.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38) 48 at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) 49 at com.alibaba.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:38) 50 at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) 51 at com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:98) 52 at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:98) 53 at com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:170) 54 at com.alibaba.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:52) 55 at com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:81) 56 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) 57 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) 58 at java.lang.Thread.run(Thread.java:745) 59 Caused by: org.postgresql.util.PSQLException: ERROR: deadlock detected 60 详细:Process 19540 waits for ShareLock on transaction 12520113; blocked by process 19539. 61 Process 19539 waits for ShareLock on transaction 12520112; blocked by process 19540. 62 建议:See server log for query details.
二、原因分析
从报错的提示我们知道了在数据库postgresql发生了死锁(ERROR: deadlock detected 侦测到了死锁发生),而且可以定位是在并发更新打印次数的时候发生的,正常的逻辑下,分页去不断更新收据的打印次数应该是不会出错的,在这边批量更新打印次数时出现了错误。
1.死锁是由于资源的相互竞争引起的,在update数据的时候应该是数据库行锁导致的
2.因为批量修改是一个默认的事务,所以如果没有全部修改完,索引是不会被放开的,所以才会在并发的多次访问中出现死锁,
3.研究出现问题的原因,发现最可能得是重复的行锁导致的,最终才查出原因是因为代码逻辑的原因,未传更新idList,而sql当中又进行了空的判断,导致每次都去更新全部的表数据,由于不同事务直接的相互等待,得不到资源,导致了死锁。
三、问题解决与拓展
批量更新的死锁解决方案有以下两种
1.将批量update通过for循环改成单条修改,但是这个方法对服务器的压力增大
2.在更新数据的时候进行一次筛选,将重复的数据剔除出去
在本次问题解决当中,只需将代码中传入正确的idLIst即可解决问题,因为本身进行迭代是没有进行重复upadate的。
疑问:并发update下postgresql就会出现shareLock死锁吗?
一般情况下的多次update应该不会导致死锁,而在事务当中的update则比较可能发生死锁现象。
同时,也看到了一位博主说postgresql 并发update的死锁问题可能是一些版本出现的bug,以及可能可以进行解决的设置以下两个参数进行解决:
autovacuum_vacuum_scale_factor = 0.03
autovacuum_analyze_scale_factor = 0.03
参考地址:http://blog.chinaunix.net/uid-20726500-id-4773950.html
https://blog.51cto.com/372550/2387517