<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          mysql死鎖的情況問題分析

          共 7470字,需瀏覽 15分鐘

           ·

          2021-03-07 14:02

          線上某服務時不時報出如下異常(大約一天二十多次):“Deadlock found when trying to get lock;”。

                Oh, My God! 是死鎖問題。盡管報錯不多,對性能目前看來也無太大影響,但還是需要解決,保不齊哪天成為性能瓶頸。
               為了更系統(tǒng)的分析問題,本文將從死鎖檢測、索引隔離級別與鎖的關系、死鎖成因、問題定位這五個方面來展開討論。

           圖1 應用日志

          1 死鎖是怎么被發(fā)現(xiàn)的?

          1.1 死鎖成因&&檢測方法

               左圖那兩輛車造成死鎖了嗎?不是!右圖四輛車造成死鎖了嗎?是!

                                                                                圖2 死鎖描述

                我們mysql用的存儲引擎是innodb,從日志來看,innodb主動探知到死鎖,并回滾了某一苦苦等待的事務。問題來了,innodb是怎么探知死鎖的?

               直觀方法是在兩個事務相互等待時,當一個等待時間超過設置的某一閥值時,對其中一個事務進行回滾,另一個事務就能繼續(xù)執(zhí)行。這種方法簡單有效,在innodb中,參數(shù)innodb_lock_wait_timeout用來設置超時時間。

               僅用上述方法來檢測死鎖太過被動,innodb還提供了wait-for graph算法來主動進行死鎖檢測,每當加鎖請求無法立即滿足需要并進入等待時,wait-for graph算法都會被觸發(fā)。

          1.2 wait-for graph原理

               我們怎么知道上圖中四輛車是死鎖的?他們相互等待對方的資源,而且形成環(huán)路!我們將每輛車看為一個節(jié)點,當節(jié)點1需要等待節(jié)點2的資源時,就生成一條有向邊指向節(jié)點2,最后形成一個有向圖。我們只要檢測這個有向圖是否出現(xiàn)環(huán)路即可,出現(xiàn)環(huán)路就是死鎖!這就是wait-for graph算法。
                                                                                                      圖3 wait for graph

               innodb將各個事務看為一個個節(jié)點,資源就是各個事務占用的鎖,當事務1需要等待事務2的鎖時,就生成一條有向邊從1指向2,最后行成一個有向圖。

          1.2 innodb隔離級別、索引與鎖 

                死鎖檢測是死鎖發(fā)生時innodb給我們的救命稻草,我們需要它,但我們更需要的是避免死鎖發(fā)生的能力,如何盡可能避免?這需要了解innodb中的鎖。

          1.2.1 鎖與索引的關系

                 假設我們有一張消息表(msg),里面有3個字段。假設id是主鍵,token是非唯一索引,message沒有索引。

          id: bigint

          token: varchar(30)

          message: varchar(4096)

               innodb對于主鍵使用了聚簇索引,這是一種數(shù)據(jù)存儲方式,表數(shù)據(jù)是和主鍵一起存儲,主鍵索引的葉結(jié)點存儲行數(shù)據(jù)。對于普通索引,其葉子節(jié)點存儲的是主鍵值。

                                                                                           圖4 聚簇索引和二級索引
               下面分析下索引和鎖的關系。
          1)delete from msg where id=2;

               由于id是主鍵,因此直接鎖住整行記錄即可。
                                                                                         圖5
          2)delete from msg where token=’ cvs’;

              由于token是二級索引,因此首先鎖住二級索引(兩行),接著會鎖住相應主鍵所對應的記錄;
                                                                                 圖6
          3)delete from msg where message=訂單號是多少’;

               message沒有索引,所以走的是全表掃描過濾。這時表上的各個記錄都將添加上X鎖。
                                                                                  圖7

          1.2.2 鎖與隔離級別的關系

               大學數(shù)據(jù)庫原理都學過,為了保證并發(fā)操作數(shù)據(jù)的正確性,數(shù)據(jù)庫都會有事務隔離級別的概念:1)未提交讀(Read uncommitted);2)已提交讀(Read committed(RC));3)可重復讀(Repeatable read(RR));4)可串行化(Serializable)。我們較常使用的是RC和RR。

               提交讀(RC):只能讀取到已經(jīng)提交的數(shù)據(jù)。

               可重復讀(RR):在同一個事務內(nèi)的查詢都是事務開始時刻一致的,InnoDB默認級別。

               我們在1.2.1節(jié)談論的其實是RC隔離級別下的鎖,它可以防止不同事務版本的數(shù)據(jù)修改提交時造成數(shù)據(jù)沖突的情況,但當別的事務插入數(shù)據(jù)時可能會出現(xiàn)問題。

                 如下圖所示,事務A在第一次查詢時得到1條記錄,在第二次執(zhí)行相同查詢時卻得到兩條記錄。從事務A角度上看是見鬼了!這就是幻讀,RC級別下盡管加了行鎖,但還是避免不了幻讀。

                                                                               圖8

               innodb的RR隔離級別可以避免幻讀發(fā)生,怎么實現(xiàn)?當然需要借助于鎖了!

               為了解決幻讀問題,innodb引入了gap鎖。

                在事務A執(zhí)行:update msg set message=‘訂單’ where token=‘a(chǎn)sd’;

                innodb首先會和RC級別一樣,給索引上的記錄添加上X鎖,此外,還在非唯一索引’asd’與相鄰兩個索引的區(qū)間加上鎖。

                 這樣,當事務B在執(zhí)行insert into msg values (null,‘a(chǎn)sd',’hello’); commit;時,會首先檢查這個區(qū)間是否被鎖上,如果被鎖上,則不能立即執(zhí)行,需要等待該gap鎖被釋放。這樣就能避免幻讀問題。
                                                                                     圖9

               推薦一篇好文,可以深入理解鎖的原理:http://hedengcheng.com/?p=771#_Toc374698322

          3 死鎖成因

               了解了innodb鎖的基本原理后,下面分析下死鎖的成因。如前面所說,死鎖一般是事務相互等待對方資源,最后形成環(huán)路造成的。下面簡單講下造成相互等待最后形成環(huán)路的例子。

          3.1不同表相同記錄行鎖沖突

               這種情況很好理解,事務A和事務B操作兩張表,但出現(xiàn)循環(huán)等待鎖情況。

                                                                                 圖10

          3.2相同表記錄行鎖沖突

               這種情況比較常見,之前遇到兩個job在執(zhí)行數(shù)據(jù)批量更新時,jobA處理的的id列表為[1,2,3,4],而job處理的id列表為[8,9,10,4,2],這樣就造成了死鎖。

                                                                                    圖11

          3.3不同索引鎖沖突

               這種情況比較隱晦,事務A在執(zhí)行時,除了在二級索引加鎖外,還會在聚簇索引上加鎖,在聚簇索引上加鎖的順序是[1,4,2,3,5],而事務B執(zhí)行時,只在聚簇索引上加鎖,加鎖順序是[1,2,3,4,5],這樣就造成了死鎖的可能性。

                                                                                    圖12

          3.4 gap鎖沖突

               innodb在RR級別下,如下的情況也會產(chǎn)生死鎖,比較隱晦。不清楚的同學可以自行根據(jù)上節(jié)的gap鎖原理分析下。
                                                                                         圖13

          4 如何盡可能避免死鎖

          1)以固定的順序訪問表和行。比如對第2節(jié)兩個job批量更新的情形,簡單方法是對id列表先排序,后執(zhí)行,這樣就避免了交叉等待鎖的情形;又比如對于3.1節(jié)的情形,將兩個事務的sql順序調(diào)整為一致,也能避免死鎖。

          2)大事務拆小。大事務更傾向于死鎖,如果業(yè)務允許,將大事務拆小。

          3)在同一個事務中,盡可能做到一次鎖定所需要的所有資源,減少死鎖概率。

          4)降低隔離級別。如果業(yè)務允許,將隔離級別調(diào)低也是較好的選擇,比如將隔離級別從RR調(diào)整為RC,可以避免掉很多因為gap鎖造成的死鎖。

          5)為表添加合理的索引。可以看到如果不走索引將會為表的每一行記錄添加上鎖,死鎖的概率大大增大。

          5 如何定位死鎖成因

               下面以本文開頭的死鎖案例為例,講下如何排查死鎖成因。

          1)通過應用業(yè)務日志定位到問題代碼,找到相應的事務對應的sql;

                因為死鎖被檢測到后會回滾,這些信息都會以異常反應在應用的業(yè)務日志中,通過這些日志我們可以定位到相應的代碼,并把事務的sql給梳理出來。

          1

          2

          3

          4

          5

          start tran

          1 deleteHeartCheckDOByToken

          2 updateSessionUser

          ...

          commit

                此外,我們根據(jù)日志回滾的信息發(fā)現(xiàn)在檢測出死鎖時這個事務被回滾。

          2)確定數(shù)據(jù)庫隔離級別。

               執(zhí)行select @@global.tx_isolation,可以確定數(shù)據(jù)庫的隔離級別,我們數(shù)據(jù)庫的隔離級別是RC,這樣可以很大概率排除gap鎖造成死鎖的嫌疑;

          3)找DBA執(zhí)行下show InnoDB STATUS看看最近死鎖的日志。

               這個步驟非常關鍵。通過DBA的幫忙,我們可以有更為詳細的死鎖信息。通過此詳細日志一看就能發(fā)現(xiàn),與之前事務相沖突的事務結(jié)構(gòu)如下:

          1

          2

          3

          4

          5

          start tran

          1 updateSessionUser

          2 deleteHeartCheckDOByToken

          ...

          commit

            這不就是圖10描述的死鎖嘛!

           


          以上都是別人總結(jié)的,具體還可以參考這個文章,寫的不錯:http://hedengcheng.com/?p=771#_Toc374698322

          瀏覽 90
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线观看亚洲免费视频 | 欧美日皮片子免费 | 久热中文字幕无均码在线观看 | 成人做爰A片免费看网站找不到了 | 人人妻人人澡人人爽人人DVD |