共計(jì) 3104 個(gè)字符,預(yù)計(jì)需要花費(fèi) 8 分鐘才能閱讀完成。
本篇內(nèi)容介紹了“怎么理解 MySQL wwwhj8828coml8o88O49999 死鎖問題”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓丸趣 TV 小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
1 應(yīng)用日志
1 死鎖是怎么被發(fā)現(xiàn)的?
1.1 死鎖成因 檢測(cè)方法
我們 mysql 用的存儲(chǔ)引擎是 innodb,從日志來(lái)看,innodb 主動(dòng)探知到死鎖,并回滾了某一苦苦等待的事務(wù)。問題來(lái)了,innodb 是怎么探知死鎖的?直觀方法是在兩個(gè)事務(wù)相互等待時(shí),當(dāng)一個(gè)等待時(shí)間超過設(shè)置的某一閥值時(shí),對(duì)其中一個(gè)事務(wù)進(jìn)行回滾,另一個(gè)事務(wù)就能繼續(xù)執(zhí)行。這種方法簡(jiǎn)單有效,在 innodb 中,參數(shù) innodb_lock_wait_timeout 用來(lái)設(shè)置超時(shí)時(shí)間。
僅用上述方法來(lái)檢測(cè)死鎖太過被動(dòng),innodb 還提供了 wait-for graph 算法來(lái)主動(dòng)進(jìn)行死鎖檢測(cè),每當(dāng)加鎖請(qǐng)求無(wú)法立即滿足需要并進(jìn)入等待時(shí),wait-for graph 算法都會(huì)被觸發(fā)。
1.2 wait-for graph 原理
我們?cè)趺粗郎蠄D中四輛車是死鎖的?他們相互等待對(duì)方的資源,而且形成環(huán)路!我們將每輛車看為一個(gè)節(jié)點(diǎn),當(dāng)節(jié)點(diǎn) 1 需要等待節(jié)點(diǎn) 2 的資源時(shí),就生成一條有向邊指向節(jié)點(diǎn) 2,最后形成一個(gè)有向圖。我們只要檢測(cè)這個(gè)有向圖是否出現(xiàn)環(huán)路即可,出現(xiàn)環(huán)路就是死鎖!這就是 wait-for graph 算法。
innodb 將各個(gè)事務(wù)看為一個(gè)個(gè)節(jié)點(diǎn),資源就是各個(gè)事務(wù)占用的鎖,當(dāng)事務(wù) 1 需要等待事務(wù) 2 的鎖時(shí),就生成一條有向邊從 1 指向 2,最后行成一個(gè)有向圖。
1.2 innodb 隔離級(jí)別、索引與鎖
死鎖檢測(cè)是死鎖發(fā)生時(shí) innodb 給我們的救命稻草,我們需要它,但我們更需要的是避免死鎖發(fā)生的能力,如何盡可能避免?這需要了解 innodb 中的鎖。
1.2.1 鎖與索引的關(guān)系
假設(shè)我們有一張消息表(msg),里面有 3 個(gè)字段。假設(shè) id 是主鍵,token 是非唯一索引,message 沒有索引。
id: biginttoken: varchar(30)message: varchar(4096)
innodb 對(duì)于主鍵使用了聚簇索引,這是一種數(shù)據(jù)存儲(chǔ)方式,表數(shù)據(jù)是和主鍵一起存儲(chǔ),主鍵索引的葉結(jié)點(diǎn)存儲(chǔ)行數(shù)據(jù)。對(duì)于普通索引,其葉子節(jié)點(diǎn)存儲(chǔ)的是主鍵值。
下面分析下索引和鎖的關(guān)系。
1)delete from msg where id=2;
由于 id 是主鍵,因此直接鎖住整行記錄即可。
2)delete from msg where token=’cvs’;
由于 token 是二級(jí)索引,因此首先鎖住二級(jí)索引(兩行),接著會(huì)鎖住相應(yīng)主鍵所對(duì)應(yīng)的記錄;
3)delete from msg where message= 訂單號(hào)是多少’;
message 沒有索引,所以走的是全表掃描過濾。這時(shí)表上的各個(gè)記錄都將添加上 X 鎖。
1.2.2 鎖與隔離級(jí)別的關(guān)系
大學(xué)數(shù)據(jù)庫(kù)原理都學(xué)過,為了保證并發(fā)操作數(shù)據(jù)的正確性,數(shù)據(jù)庫(kù)都會(huì)有事務(wù)隔離級(jí)別的概念:1)未提交讀(Read uncommitted);2)已提交讀(Read committed(RC));3)可重復(fù)讀(Repeatable read(RR));4)可串行化(Serializable)。我們較常使用的是 RC 和 RR。
提交讀 (RC):只能讀取到已經(jīng)提交的數(shù)據(jù)。
可重復(fù)讀 (RR):在同一個(gè)事務(wù)內(nèi)的查詢都是事務(wù)開始時(shí)刻一致的,InnoDB 默認(rèn)級(jí)別。
我們?cè)?1.2.1 節(jié)談?wù)摰钠鋵?shí)是 RC 隔離級(jí)別下的鎖,它可以防止不同事務(wù)版本的數(shù)據(jù)修改提交時(shí)造成數(shù)據(jù)沖突的情況,但當(dāng)別的事務(wù)插入數(shù)據(jù)時(shí)可能會(huì)出現(xiàn)問題。
如下圖所示,事務(wù) A 在第一次查詢時(shí)得到 1 條記錄,在第二次執(zhí)行相同查詢時(shí)卻得到兩條記錄。從事務(wù) A 角度上看是見鬼了!這就是幻讀,RC 級(jí)別下盡管加了行鎖,但還是避免不了幻讀。
innodb 的 RR 隔離級(jí)別可以避免幻讀發(fā)生,怎么實(shí)現(xiàn)?當(dāng)然需要借助于鎖了!
為了解決幻讀問題,innodb 引入了 gap 鎖。
在事務(wù) A 執(zhí)行:update msg set message=‘訂單’where token=‘a(chǎn)sd’;
innodb 首先會(huì)和 RC 級(jí)別一樣,給索引上的記錄添加上 X 鎖,此外,還在非唯一索引’asd’與相鄰兩個(gè)索引的區(qū)間加上鎖。
這樣,當(dāng)事務(wù) B 在執(zhí)行 insert into msg values (null,‘a(chǎn)sd’,’hello’); commit; 時(shí),會(huì)首先檢查這個(gè)區(qū)間是否被鎖上,如果被鎖上,則不能立即執(zhí)行,需要等待該 gap 鎖被釋放。這樣就能避免幻讀問題。
3 死鎖成因
了解了 innodb 鎖的基本原理后,下面分析下死鎖的成因。如前面所說,死鎖一般是事務(wù)相互等待對(duì)方資源,最后形成環(huán)路造成的。下面簡(jiǎn)單講下造成相互等待最后形成環(huán)路的例子。
3.1 不同表相同記錄行鎖沖突
這種情況很好理解,事務(wù) A 和事務(wù) B 操作兩張表,但出現(xiàn)循環(huán)等待鎖情況。
3.2 相同表記錄行鎖沖突
這種情況比較常見,之前遇到兩個(gè) job 在執(zhí)行數(shù)據(jù)批量更新時(shí),jobA 處理的的 id 列表為 [1,2,3,4],而 job 處理的 id 列表為 [8,9,10,4,2],這樣就造成了死鎖。
3.3 不同索引鎖沖突
這種情況比較隱晦,事務(wù) A 在執(zhí)行時(shí),除了在二級(jí)索引加鎖外,還會(huì)在聚簇索引上加鎖,在聚簇索引上加鎖的順序是 [1,4,2,3,5],而事務(wù) B 執(zhí)行時(shí),只在聚簇索引上加鎖,加鎖順序是 [1,2,3,4,5],這樣就造成了死鎖的可能性。
3.4 gap 鎖沖突
innodb 在 RR 級(jí)別下,如下的情況也會(huì)產(chǎn)生死鎖,比較隱晦。不清楚的同學(xué)可以自行根據(jù)上節(jié)的 gap 鎖原理分析下。
4 如何盡可能避免死鎖
1)以固定的順序訪問表和行。比如對(duì)第 2 節(jié)兩個(gè) job 批量更新的情形,簡(jiǎn)單方法是對(duì) id 列表先排序,后執(zhí)行,這樣就避免了交叉等待鎖的情形;又比如對(duì)于 3.1 節(jié)的情形,將兩個(gè)事務(wù)的 sql 順序調(diào)整為一致,也能避免死鎖。
2)大事務(wù)拆小。大事務(wù)更傾向于死鎖,如果業(yè)務(wù)允許,將大事務(wù)拆小。
3)在同一個(gè)事務(wù)中,盡可能做到一次鎖定所需要的所有資源,減少死鎖概率。
4)降低隔離級(jí)別。如果業(yè)務(wù)允許,將隔離級(jí)別調(diào)低也是較好的選擇,比如將隔離級(jí)別從 RR 調(diào)整為 RC,可以避免掉很多因?yàn)?gap 鎖造成的死鎖。
5)為表添加合理的索引。可以看到如果不走索引將會(huì)為表的每一行記錄添加上鎖,死鎖的概率大大增大。
5 如何定位死鎖成因
下面以本文開頭的死鎖案例為例,講下如何排查死鎖成因。
1)通過應(yīng)用業(yè)務(wù)日志定位到問題代碼,找到相應(yīng)的事務(wù)對(duì)應(yīng)的 sql;
因?yàn)樗梨i被檢測(cè)到后會(huì)回滾,這些信息都會(huì)以異常反應(yīng)在應(yīng)用的業(yè)務(wù)日志中,通過這些日志我們可以定位到相應(yīng)的代碼,并把事務(wù)的 sql 給梳理出來(lái)。
start tran
1 deleteHeartCheckDOByToken
2 updateSessionUser
…
commit
此外,我們根據(jù)日志回滾的信息發(fā)現(xiàn)在檢測(cè)出死鎖時(shí)這個(gè)事務(wù)被回滾。
2)確定數(shù)據(jù)庫(kù)隔離級(jí)別。
執(zhí)行 select @@global.tx_isolation,可以確定數(shù)據(jù)庫(kù)的隔離級(jí)別,我們數(shù)據(jù)庫(kù)的隔離級(jí)別是 RC,這樣可以很大概率排除 gap 鎖造成死鎖的嫌疑;
3)找 DBA 執(zhí)行下 show InnoDB STATUS 看看最近死鎖的日志。
這個(gè)步驟非常關(guān)鍵。通過 DBA 的幫忙,我們可以有更為詳細(xì)的死鎖信息。通過此詳細(xì)日志一看就能發(fā)現(xiàn),與之前事務(wù)相沖突的事務(wù)結(jié)構(gòu)如下:
start tran
1 updateSessionUser
2 deleteHeartCheckDOByToken
…
commit
“怎么理解 MySQL wwwhj8828coml8o88O49999 死鎖問題”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注丸趣 TV 網(wǎng)站,丸趣 TV 小編將為大家輸出更多高質(zhì)量的實(shí)用文章!