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