MySQL死鎖原因和處理方案
MySQL死鎖原因和處理方案
本文件記錄工作過程發現的死鎖(DeadLock)問題的原因分析和處理方法
案例一:業務流程對中間表做更新操作,更新方式是先根據單據ID刪除再新增,併發時出現死鎖。
死鎖日誌:
------------------------ LATESTDETECTEDDEADLOCK ------------------------ 2020-10-2415:40:220x7fcf7b820700 ***(1)TRANSACTION: TRANSACTION49624342,ACTIVE7secinserting mysqltablesinuse1,locked1 LOCKWAIT5lockstruct(s),heapsize1136,2rowlock(s),undologentries5 MySQLthreadid241951,OSthreadhandle140529097492224,queryid15368812210.3.98.155rootupdate insertintounicom_biz_send_recv_middle(id,order_id,......)values('7131cadade2e4b45b734a8c8eab4e44a','e30c7f8b104345a18fd5e705936efe36',...... ***(1)WAITINGFORTHISLOCKTOBEGRANTED: RECORDLOCKSspaceid24755pageno38nbits264indexidx_order_idoftable`sceo_integration`.`unicom_biz_send_recv_middle`trxid49624342lock_modeXlocksgapbeforerecinsertintentionwaiting Recordlock,heapno190PHYSICALRECORD:n_fields2;compactformat;infobits32 0:len6;hex653331653730;asce31e70;; 1:len30;hex623466633037343535363064343465626263616139393562303036356437;ascb4fc0745560d44ebbcaa995b0065d7;(total32bytes); ***(2)TRANSACTION: TRANSACTION49624312,ACTIVE7secinserting mysqltablesinuse1,locked1 26lockstruct(s),heapsize3520,20rowlock(s),undologentries26 MySQLthreadid241753,OSthreadhandle140529107076864,queryid15368886910.3.98.155rootupdate insertintounicom_biz_send_recv_middle(id,order_id,......)values('0ed7678397e94202aa481757e2324d2a','e31e70f4fd1d454ab13e8d3f117656f7',...... ***(2)HOLDSTHELOCK(S): RECORDLOCKSspaceid24755pageno38nbits264indexidx_order_idoftable`sceo_integration`.`unicom_biz_send_recv_middle`trxid49624312lock_modeXlocksgapbeforerec Recordlock,heapno178PHYSICALRECORD:n_fields2;compactformat;infobits0 0:len6;hex653336653339;asce36e39;; 1:len30;hex343231353563333038343564346165376163323536653636323736333966;asc42155c30845d4ae7ac256e6627639f;(total32bytes); Recordlock,heapno190PHYSICALRECORD:n_fields2;compactformat;infobits32 0:len6;hex653331653730;asce31e70;; 1:len30;hex623466633037343535363064343465626263616139393562303036356437;ascb4fc0745560d44ebbcaa995b0065d7;(total32bytes); ***(2)WAITINGFORTHISLOCKTOBEGRANTED: RECORDLOCKSspaceid24755pageno38nbits264indexidx_order_idoftable`sceo_integration`.`unicom_biz_send_recv_middle`trxid49624312lock_modeXlocksgapbeforerecinsertintentionwaiting Recordlock,heapno190PHYSICALRECORD:n_fields2;compactformat;infobits32 0:len6;hex653331653730;asce31e70;; 1:len30;hex623466633037343535363064343465626263616139393562303036356437;ascb4fc0745560d44ebbcaa995b0065d7;(total32bytes); ***WEROLLBACKTRANSACTION(1)
上面日誌中lock_modeXlocksgapbeforerecinsertintentionwaiting
表示兩個事務同時持有間隙鎖,並且都在等待插入意向鎖。根據索引欄位order_id
對當前資料庫中已有資料做排序:
id | order_id |
---|---|
d080180e908a4a20959b8991e3fb2daa | e37f5317f2e1450a9cd69cfb00543f3d |
8ff5ce0eada34aeba20e651c60d0201f | e37d872e0a6d4be0877064f06f70a994 |
42155c30845d4ae7ac256e6627639f3f | e36e39e5a5674956961a95043ab0592d |
8de655e142ba4f2fb6346d5fbce7aee4 | e2d8fd5ecfa9484a881a2fb073ae3d9b |
73dc37233255495d904e81967792f954 | e27dcbdecc0546caa4bec447e33a8767 |
547d1fc2b1604c8c90595e8c4b78faeb | e27b85294ad8435897cce0029b829db0 |
414ab95ce70849ebabc2dc59c6a7a218 | e272a595a55642c79cbb600da6a0455f |
可以發現日誌中兩個事務準備insert的兩條資料的order_id
的值:e30c7f...
e31e70...
排序後剛好都落在e36e39...
和e2d8fd...
之間,這導致兩個事務的上一步delete操作都能拿到(
e36e39...
,e2d8fd...
]這個區間的間隙鎖(GapLock)。
接下來事務(1)的insert操作發現當前事務持有間隙鎖(GapLock)則會請求插入意向鎖(InsertIntentionLock),因為插入意向鎖(InsertIntentionLock)與其他事務的間隙鎖(GapLock)互斥,所以事務(1)請求的插入意向鎖(InsertIntentionLock)會一直處於阻塞狀態(即日誌中的 insertintentionwaiting),並等待其他事務釋放該區間的間隙鎖(GapLock),INFORMATION_SCHEMA.INNODB_LOCKS 中能看到兩個事務的持鎖情況如下:
lock_trx_id | lock_mode | lock_type | lock_index | lock_space | lock_page | lock_rec | lock_data |
---|---|---|---|---|---|---|---|
49944201 | X,GAP | RECORD | idx_order_id | 34191 | 39 | 105 | 'e36e39','42155c30845d4ae7ac256e6627639f3f' |
49944184 | X,GAP | RECORD | idx_order_id | 34191 | 39 | 105 | 'e36e39','42155c30845d4ae7ac256e6627639f3f' |
此時如果事務(2)也請求該區間的插入意向鎖(InsertIntentionLock),則MySQL直接認為出現死鎖(DeadLock),並選擇一個其認為影響較小的事務進行回滾,以讓另一個事務繼續下去。
案例一處理方案:
根據非唯一索引刪除一條不存在的記錄時才會產生間隙鎖(GapLock),如果記錄存在則不會產生間隙鎖(GapLock),該案例的刪除操作應改為根據主鍵刪除,即刪除前根據索引欄位order_id
查詢,如果有記錄返回再根據主鍵進行刪除。
案例二:迴圈根據主鍵更新表資料,併發時出現死鎖問題,死鎖日誌:
------------------------
LATESTDETECTEDDEADLOCK
------------------------
2020-10-2814:13:420x7f8051206700
***(1)TRANSACTION:
TRANSACTION22272963,ACTIVE27secstartingindexread
mysqltablesinuse1,locked1
LOCKWAIT3lockstruct(s),heapsize1136,2rowlock(s),undologentries1
MySQLthreadid54270,OSthreadhandle140189101999872,queryid67632913hf-004239.hkhf.hkgp.net10.3.99.20zhaomjupdating
updateinv_lot_inventory_copysetpick_qty=0,inv_qty=10whereid='317270bd380b49869bdb8abad69e080a'
***(1)WAITINGFORTHISLOCKTOBEGRANTED:
RECORDLOCKSspaceid30877pageno12nbits88indexPRIMARYoftable`yongjia`.`inv_lot_inventory_copy`trxid22272963lock_modeXlocksrecbutnotgapwaiting
Recordlock,heapno9PHYSICALRECORD:n_fields82;compactformat;infobits0
0:len30;hex333137323730626433383062343938363962646238616261643639653038;asc317270bd380b49869bdb8abad69e08;(total32bytes);
***(2)TRANSACTION:
TRANSACTION22272966,ACTIVE23secstartingindexread
mysqltablesinuse1,locked1
3lockstruct(s),heapsize1136,2rowlock(s),undologentries1
MySQLthreadid54271,OSthreadhandle140189093619456,queryid67632952hf-004239.hkhf.hkgp.net10.3.99.20zhaomjupdating
updateinv_lot_inventory_copysetpick_qty=0,inv_qty=10whereid='23ea1059baf7441db2b3063836d6dad2'
***(2)HOLDSTHELOCK(S):
RECORDLOCKSspaceid30877pageno12nbits88indexPRIMARYoftable`yongjia`.`inv_lot_inventory_copy`trxid22272966lock_modeXlocksrecbutnotgap
Recordlock,heapno9PHYSICALRECORD:n_fields82;compactformat;infobits0
0:len30;hex333137323730626433383062343938363962646238616261643639653038;asc317270bd380b49869bdb8abad69e08;(total32bytes);
***(2)WAITINGFORTHISLOCKTOBEGRANTED:
RECORDLOCKSspaceid30877pageno12nbits88indexPRIMARYoftable`yongjia`.`inv_lot_inventory_copy`trxid22272966lock_modeXlocksrecbutnotgapwaiting
Recordlock,heapno3PHYSICALRECORD:n_fields82;compactformat;infobits0
0:len30;hex323365613130353962616637343431646232623330363338333664366461;asc23ea1059baf7441db2b3063836d6da;(total32bytes);
***WEROLLBACKTRANSACTION(2)
日誌中locksrecbutnotgap
表示這兩個事務只是在等待行鎖(RecordLock),其中事務(2)持有主鍵索引為317270...
的行鎖(RecordLock),並等待主鍵索引為23ea10...
的行鎖(RecordLock);而事務(1)的日誌並不完整,只能看到在等待主鍵索引為317270b...
的行鎖(RecordLock),結合業務日誌發現事務(一)和事務(2)迴圈更新批號庫存表(inv_lot_inventory_copy)的順序分別是:
事務(1):
- 23ea1059baf7441db2b3063836d6dad2
- 317270bd380b49869bdb8abad69e080a
事務(2):
- 317270bd380b49869bdb8abad69e080a
- 23ea1059baf7441db2b3063836d6dad2
因此推斷出事務(1)等待主鍵索引為 317270...
的行鎖(RecordLock)同時也持有主鍵索引為 23ea10...
的行鎖(RecordLock)。
當事務(2)開始處理第二條資料時會進入阻塞狀態(locksrecbutnotgapwaiting),等待事務一釋放其持有的主鍵索引為 23ea10...
的行鎖(RecordLock),等待的同時,事務(1)開始處理它的第二條資料,而事務(1)處理第二條資料所需的行鎖(RecordLock)剛好被事務(2)持有,此時MySQL判定發生了死鎖,回滾了事務(2)。
案例二處理方案:
此類死鎖,可以通過給需要處理的資料統一按主鍵或索引欄位(取決於更新的條件)排序來解決,保證每個事務處理資料的順序一致即可。例如此案例中將兩個事務要處理的資料都按主鍵排序:
事務(1):
- 23ea1059baf7441db2b3063836d6dad2
- 317270bd380b49869bdb8abad69e080a
事務(2):
- 23ea1059baf7441db2b3063836d6dad2
- 317270bd380b49869bdb8abad69e080a
這樣,如果事務(1)先拿到主鍵索引為 23ea10...
的行鎖(RecordLock),事務(2)開始就會阻塞,不會再拿到主鍵索引為 317270...
的行鎖(RecordLock),直到事務(1)提交或回滾。
部分單據在提交、審批和通過時會對同一張表的同一批資料做更新操作,例如案例二的出入庫單,審批時可以修改批號庫存的撿料數量,通過時扣減批號庫存的庫存數量,先後的兩次操作並沒有問題,但是如果將流程配置為自動通過或者後臺呼叫了流程的自動通過介面,則會導致所有的修改被合併到一個事務中,這時候事務要處理的資料順序可能是這樣的:
事務(1):
- 317270bd380b49869bdb8abad69e080a(審批操作修改)
- 23ea1059baf7441db2b3063836d6dad2(通過操作修改)
- 317270bd380b49869bdb8abad69e080a(通過操作修改)
事務(2):
- 23ea1059baf7441db2b3063836d6dad2
- 317270bd380b49869bdb8abad69e080a
這樣兩個事務仍會死鎖,解決方案是將中間過程要修改的資料或是差異記錄到單據上,最終將修改內容合併到流程通過的UPDATE語句中。
參考閱讀: