1. 程式人生 > 其它 >MySQL死鎖原因和處理方案

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語句中。

參考閱讀:

  1. MySQLLock--gapbeforerecinsertintentionwaiting
  2. Mysql鎖詳解(行鎖、表鎖、意向鎖、Gap鎖、插入意向鎖)
  3. mysql併發insert死鎖問題——gap、插入意向鎖衝突
  4. 解決死鎖之路-常見SQL語句的加鎖分析