日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

MySQL死锁如何处理

發(fā)布時(shí)間:2023/12/3 数据库 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MySQL死锁如何处理 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(zhuǎn)載自??MySQL死鎖如何處理

前提

筆者負(fù)責(zé)的一個(gè)系統(tǒng)最近有新功能上線后突然在預(yù)警模塊不定時(shí)報(bào)出MySQL死鎖導(dǎo)致事務(wù)回滾。幸虧,上游系統(tǒng)采用了異步推送和同步查詢結(jié)合的方式,感知到推送失敗及時(shí)進(jìn)行了補(bǔ)償。于是,筆者爭取了一點(diǎn)時(shí)間詳細(xì)分析了導(dǎo)致死鎖的多個(gè)事務(wù)的執(zhí)行時(shí)序,分析并且得出解決方案。

死鎖場景復(fù)現(xiàn)

首先,MySQL的服務(wù)端版本是5.7(小版本可以基本忽略),使用了InnoDB。有一張用戶數(shù)據(jù)表的schema設(shè)計(jì)如下(無關(guān)字段已經(jīng)屏蔽掉):

CREATE?TABLE?`t_user_data` (id??????BIGINT?UNSIGNED?PRIMARY?KEY?AUTO_INCREMENT,user_id?BIGINT?UNSIGNED?NOT?NULL?COMMENT?'用戶ID',data_id?VARCHAR(50)?????NOT?NULL?COMMENT?'數(shù)據(jù)ID',INDEX?idx_user_id?(user_id),INDEX?idx_data_id?(data_id) )?COMMENT?'用戶數(shù)據(jù)表';

業(yè)務(wù)代碼中發(fā)生死鎖的偽代碼如下:

process_method(dataId,userDataDtoList){start?transaction:userDataDao.deleteByDataId(dataId);for?dto?in?userDataDtoList:UserData?userData?=?convert(dto);userDataDao.insert(dto);commit; }

這里的邏輯是,如果已經(jīng)存在對應(yīng)dataId的數(shù)據(jù)要先進(jìn)行刪除,然后寫入新的用戶數(shù)據(jù)。

嘗試用兩個(gè)Session提交兩個(gè)事務(wù)重現(xiàn)死鎖問題:

時(shí)間序列Tx-Session-1Tx-Session-2
T1START TRANSACTION;?
T2?START TRANSACTION;
T3DELETE FROM t_user_data WHERE data_id = ‘xxxxx’;?
T4?DELETE FROM t_user_data WHERE data_id = ‘yyyyy’;
T5INSERT INTO t_user_data(USER_ID, DATA_ID) VALUES (1, ‘xxxxx’);?
T6?INSERT INTO t_user_data(USER_ID, DATA_ID) VALUES (2, ‘yyyyy’);
T7?Deadlock found when trying to get lock; try restarting transaction(Rollback)
T8COMMIT;?

這里會(huì)出現(xiàn)兩個(gè)現(xiàn)象:

  • Tx-Session-2會(huì)話T4執(zhí)行完畢之后,Tx-Session-1會(huì)話T5執(zhí)行的時(shí)候,Tx-Session-1會(huì)話客戶端會(huì)處于阻塞狀態(tài)。

  • Tx-Session-2會(huì)話T6執(zhí)行完畢之后,MySQL提示死鎖事務(wù)被回滾,此時(shí),Tx-Session-1會(huì)話客戶端會(huì)解除阻塞。

  • 導(dǎo)致死鎖的原因

    后面會(huì)寫一篇專門的文章學(xué)習(xí)和理解MySQL的InnoDB數(shù)據(jù)引擎的鎖相關(guān)知識(shí),這里直接排查InnoDB的死鎖日志。

    mysql>?show?engine?innodb?status;

    輸出的死鎖日志如下:

    ------------------------ LATEST?DETECTED?DEADLOCK ------------------------ 2019-05-11?19:16:04?0x5804 ***?(1)?TRANSACTION: TRANSACTION?3882,?ACTIVE?13?sec?inserting mysql?tables?in?use?1,?locked?1 LOCK?WAIT?3?lock?struct(s),?heap?size?1136,?2?row?lock(s),?undo?log?entries?1 MySQL?thread?id?32,?OS?thread?handle?9876,?query?id?358?localhost?::1?doge?update INSERT?INTO?t_user_data(USER_ID,?DATA_ID)?VALUES?(1,?'xxxxx') ***?(1)?WAITING?FOR?THIS?LOCK?TO?BE?GRANTED: RECORD?LOCKS?space?id?33?page?no?6?n?bits?72?index?idx_data_id?of?table?`test`.`t_user_data`?trx?id?3882?lock_mode?X?insert?intention?waiting Record?lock,?heap?no?1?PHYSICAL?RECORD:?n_fields?1;?compact?format;?info?bits?00:?len?8;?hex?73757072656d756d;?asc?supremum;;***?(2)?TRANSACTION: TRANSACTION?3883,?ACTIVE?9?sec?inserting,?thread?declared?inside?InnoDB?5000 mysql?tables?in?use?1,?locked?1 3?lock?struct(s),?heap?size?1136,?2?row?lock(s),?undo?log?entries?1 MySQL?thread?id?11,?OS?thread?handle?22532,?query?id?359?localhost?::1?doge?update INSERT?INTO?t_user_data(USER_ID,?DATA_ID)?VALUES?(2,?'yyyyy') ***?(2)?HOLDS?THE?LOCK(S): RECORD?LOCKS?space?id?33?page?no?6?n?bits?72?index?idx_data_id?of?table?`test`.`t_user_data`?trx?id?3883?lock_mode?X Record?lock,?heap?no?1?PHYSICAL?RECORD:?n_fields?1;?compact?format;?info?bits?00:?len?8;?hex?73757072656d756d;?asc?supremum;;***?(2)?WAITING?FOR?THIS?LOCK?TO?BE?GRANTED: RECORD?LOCKS?space?id?33?page?no?6?n?bits?72?index?idx_data_id?of?table?`test`.`t_user_data`?trx?id?3883?lock_mode?X?insert?intention?waiting Record?lock,?heap?no?1?PHYSICAL?RECORD:?n_fields?1;?compact?format;?info?bits?00:?len?8;?hex?73757072656d756d;?asc?supremum;;***?WE?ROLL?BACK?TRANSACTION?(2)

    這里要參考MySQL關(guān)于InnoDB鎖的關(guān)于next-key鎖描述那一節(jié),注意死鎖日志關(guān)鍵字supremum的意義:

    next-key鎖將gap鎖定在索引中最大值之上,而supremum偽記錄的值高于索引中實(shí)際的任何值。supremum不是真正的索引記錄,因此,實(shí)際上,此next-key鎖僅鎖定最大索引值之后的間隙。

    兩個(gè)事務(wù)的鎖屬性可以通過select * from information_schema.innodb_locks;進(jìn)行查詢,數(shù)據(jù)如下表:

    lock_idlock_tx_idlock_modelock_typelock_tablelock_indexlock_spacelock_pagelock_reclock_data
    3882:33:6:13882XRECORDtest.t_user_dataidx_data_id3361supremum pseudo-record
    3883:33:6:13883XRECORDtest.t_user_dataidx_data_id3361supremum pseudo-record
    DELETE?FROM?t_user_data?WHERE?data_id?=?'不存在的索引值';

    上面的SQL執(zhí)行時(shí)候,如果條件剛好是索引列,并且查詢的值是當(dāng)前表(索引)中不存在的數(shù)據(jù),根據(jù)next-key鎖的描述和死鎖日志中的asc supremum關(guān)鍵字,執(zhí)行該DELETE語句的時(shí)候,會(huì)鎖定目標(biāo)值和高于目標(biāo)值的任何值,如果條件是"xxxxx",那么相當(dāng)于鎖定區(qū)間為(“xxxxx”,最大上界]。

    next-key鎖是索引記錄上的記錄鎖(Record Lock)和索引記錄之前的間隙上的間隙鎖(Gap Lock)定的組合。間隙鎖有兩個(gè)特點(diǎn):

  • 兩個(gè)事務(wù)即使鎖定的區(qū)間一致(或者有部分重合),不會(huì)影響它們之間獲取到鎖(可以參考行鎖的兼容性矩陣)。

  • 間隙鎖G會(huì)阻止非持有G的其他事務(wù)向鎖定的區(qū)間中插入數(shù)據(jù),以避免產(chǎn)生沖突數(shù)據(jù)。

  • 分析到這里,就很好解釋上面出現(xiàn)死鎖的執(zhí)行時(shí)序:

  • 兩個(gè)事務(wù)的DELETE語句都可以正確執(zhí)行,這個(gè)時(shí)候,兩者的間隙鎖鎖定的區(qū)域分別是(‘xxxxx’,最大上界]和(‘yyyyy’,最大上界]。

  • 事務(wù)1執(zhí)行INSERT語句的時(shí)候阻塞,是因?yàn)槭聞?wù)2的間隙鎖不允許事務(wù)1插入索引值’xxxxx’。

  • 事務(wù)2執(zhí)行INSERT語句的時(shí)候阻塞,是因?yàn)槭聞?wù)1的間隙鎖不允許事務(wù)1插入索引值’yyyyy’,執(zhí)行到這一步,MySQL的死鎖檢查模塊應(yīng)該起效了,因?yàn)閮蓚€(gè)事務(wù)依賴的鎖資源已經(jīng)成環(huán)(或者成有向圖)。

  • 事務(wù)2的優(yōu)先級比較低,于是拋出死鎖異常并且被回滾了。

  • 之前曾經(jīng)和DBA同事聊過,發(fā)生死鎖的事務(wù)是怎么衡量優(yōu)先級或者怎么確定哪個(gè)事務(wù)需要回滾(釋放鎖資源讓另一個(gè)事務(wù)可以正常提交),但是后來沒有收到很好的答復(fù),這一點(diǎn)有時(shí)間再研究一下。

    解決方案

    參考MySQL的文檔,解決方案有兩個(gè):

  • 方案一:降低數(shù)據(jù)庫的事務(wù)隔離級別,需要降低到READ COMMITED,這樣子可以關(guān)閉間隙鎖的掃描。(<== 并不推薦這種做法,修改事務(wù)隔離級別有可能出現(xiàn)新的問題)

  • 方案二:針對對應(yīng)的原因修改業(yè)務(wù)代碼。

  • 這里方案二只需要把偽代碼邏輯修改如下:

    process_method(dataId,userDataDtoList){List<UserData>?userDataList?=?userDataDao.selectByDataId(dataId);start?transaction:if?userDataList?is?not?empty:?List<Long>?ids?=?collectIdList(userDataList);userDataDao.deleteByIds(ids);???????for?dto?in?userDataDtoList:UserData?userData?=?convert(dto);userDataDao.insert(dto);commit; }

    就是先根據(jù)dataId進(jìn)行查詢,如果存在數(shù)據(jù),聚合主鍵列表,通過主鍵列表進(jìn)行刪除,然后再進(jìn)行數(shù)據(jù)插入。

    小結(jié)

    這并非是第一次在生產(chǎn)環(huán)境中出現(xiàn)MySQL死鎖,只是這次的案例相對簡單。InnoDB提供的死鎖日志其實(shí)并沒有提供完整的事務(wù)提交的SQL,所以對于復(fù)雜的場景需要細(xì)致結(jié)合代碼和死鎖日志進(jìn)行排查,很多時(shí)候?qū)?yīng)的代碼邏輯是多處的。這里列舉一下筆者處理死鎖問題的一些步驟:

  • 及時(shí)止損,如果可以回滾導(dǎo)致死鎖的代碼,那么最好果敢地回滾;如果重試可以解決問題并且出現(xiàn)死鎖問題的規(guī)模不大,可以嘗試短時(shí)間內(nèi)進(jìn)行問題排查。

  • 通過業(yè)務(wù)系統(tǒng)日志迅速定位到發(fā)生死鎖的代碼塊,JVM應(yīng)用一般底層是依賴JDBC,出現(xiàn)死鎖的時(shí)候會(huì)拋出一個(gè)SQLException的子類,異常棧的信息中帶有"Deadlock"字樣。

  • 分析InnoDB的死鎖日志,一般會(huì)列出競爭鎖的多個(gè)事務(wù)的相對詳細(xì)的信息,這些信息是排查死鎖問題的第一手資料。

  • 修復(fù)問題上線后注意做好監(jiān)控和預(yù)警,確定問題徹底解決。

  • 參考資料:

    • MySQL5.7官方文檔

    總結(jié)

    以上是生活随笔為你收集整理的MySQL死锁如何处理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。