1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        面試官:說說MySQL 死鎖的場(chǎng)景、原因及如何預(yù)防死鎖?

        共 6376字,需瀏覽 13分鐘

         ·

        2021-12-27 17:43

        Mysql 鎖類型和加鎖分析

        MySQL有三種鎖的級(jí)別:頁級(jí)、表級(jí)、行級(jí)。

        表級(jí)鎖: 開銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低。

        行級(jí)鎖: 開銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度也最高。

        頁面鎖: 開銷和加鎖時(shí)間界于表鎖和行鎖之間;會(huì)出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度

        算法:

        • next KeyLocks鎖,同時(shí)鎖住記錄(數(shù)據(jù)),并且鎖住記錄前面的Gap
        • Gap鎖,不鎖記錄,僅僅記錄前面的Gap
        • Recordlock鎖(鎖數(shù)據(jù),不鎖Gap)
        • 所以其實(shí) Next-KeyLocks=Gap鎖+ Recordlock鎖

        死鎖產(chǎn)生原因和示例

        產(chǎn)生原因

        所謂死鎖:是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去.此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。表級(jí)鎖不會(huì)產(chǎn)生死鎖.所以解決死鎖主要還是針對(duì)于最常用的InnoDB。

        死鎖的關(guān)鍵在于:兩個(gè)(或以上)的Session加鎖的順序不一致。

        那么對(duì)應(yīng)的解決死鎖問題的關(guān)鍵就是:讓不同的session加鎖有次序

        產(chǎn)生示例

        案例一

        需求:將投資的錢拆成幾份隨機(jī)分配給借款人。

        起初業(yè)務(wù)程序思路是這樣的:

        投資人投資后,將金額隨機(jī)分為幾份,然后隨機(jī)從借款人表里面選幾個(gè),然后通過一條條select for update 去更新借款人表里面的余額等。

        例如兩個(gè)用戶同時(shí)投資,A用戶金額隨機(jī)分為2份,分給借款人1,2

        B用戶金額隨機(jī)分為2份,分給借款人2,1

        由于加鎖的順序不一樣,死鎖當(dāng)然很快就出現(xiàn)了。

        對(duì)于這個(gè)問題的改進(jìn)很簡(jiǎn)單,直接把所有分配到的借款人直接一次鎖住就行了。

        Select * from xxx where id in (xx,xx,xx) for update

        在in里面的列表值mysql是會(huì)自動(dòng)從小到大排序,加鎖也是一條條從小到大加的鎖

        例如(以下會(huì)話id為主鍵):

        Session1:

        mysql>?select?*?from?t3?where?id?in?(8,9)?for?update;
        +----+--------+------+---------------------+
        |?id?|?course?|?name?|?ctime???????????????|
        +----+--------+------+---------------------+
        |??8?|?WA?????|?f????|?2016-03-02?11:36:30?|
        |??9?|?JX?????|?f????|?2016-03-01?11:36:30?|
        +----+--------+------+---------------------+
        rows?in?set?(0.04?sec)
        Session2:
        select?*?from?t3?where?id?in?(10,8,5)?for?update;
        鎖等待中……

        其實(shí)這個(gè)時(shí)候id=10這條記錄沒有被鎖住的,但id=5的記錄已經(jīng)被鎖住了,鎖的等待在id=8的這里
        不信請(qǐng)看

        Session3:
        mysql>?select?*?from?t3?where?id=5?for?update;
        鎖等待中


        Session4:
        mysql>?select?*?from?t3?where?id=10?for?update;
        +----+--------+------+---------------------+
        |?id?|?course?|?name?|?ctime???????????????|
        +----+--------+------+---------------------+
        |?10?|?JB?????|?g????|?2016-03-10?11:45:05?|
        +----+--------+------+---------------------+
        row?in?set?(0.00?sec)
        在其它sessionid=5是加不了鎖的,但是id=10是可以加上鎖的。

        案例二

        在開發(fā)中,經(jīng)常會(huì)做這類的判斷需求:根據(jù)字段值查詢(有索引),如果不存在,則插入;否則更新。

        以id為主鍵為例,目前還沒有id=22的行

        Session1:
        select?*?from?t3?where?id=22?for?update;
        Empty?set?(0.00?sec)

        session2:
        select?*?from?t3?where?id=23??for?update;
        Empty?set?(0.00?sec)

        Session1:
        insert?into?t3?values(22,'ac','a',now());
        鎖等待中……

        Session2:
        insert?into?t3?values(23,'bc','b',now());
        ERROR?1213?(40001):?Deadlock?found?when?trying?to?get?lock;?try?restarting?transaction

        當(dāng)對(duì)存在的行進(jìn)行鎖的時(shí)候(主鍵),mysql就只有行鎖。當(dāng)對(duì)未存在的行進(jìn)行鎖的時(shí)候(即使條件為主鍵),mysql是會(huì)鎖住一段范圍(有g(shù)ap鎖)

        鎖住的范圍為:

        (無窮小或小于表中鎖住id的最大值,無窮大或大于表中鎖住id的最小值)

        如:如果表中目前有已有的id為(11 , 12)

        那么就鎖?。?2,無窮大)

        如果表中目前已有的id為(11 , 30)

        那么就鎖?。?1,30)

        對(duì)于這種死鎖的解決辦法是:

        insert into t3(xx,xx) on duplicate key update xx='XX';

        用mysql特有的語法來解決此問題。因?yàn)閕nsert語句對(duì)于主鍵來說,插入的行不管有沒有存在,都會(huì)只有行鎖

        案例三

        mysql>?select?*?from?t3?where?id=9?for?update;
        +----+--------+------+---------------------+
        |?id?|?course?|?name?|?ctime???????????????|
        +----+--------+------+---------------------+
        |??9?|?JX?????|?f????|?2016-03-01?11:36:30?|
        +----+--------+------+---------------------+

        row?in?set?(0.00?sec)
        Session2:
        mysql>?select?*?from?t3?where?id<20?for?update;
        鎖等待中

        Session1:
        mysql>?insert?into?t3?values(7,'ae','a',now());
        ERROR?1213?(40001):?Deadlock?found?when?trying?to?get?lock;?try?restarting?transaction

        這個(gè)跟案例一其它是差不多的情況,只是session1不按常理出牌了,

        Session2在等待Session1的id=9的鎖,session2又持了1到8的鎖(注意9到19的范圍并沒有被session2鎖?。?,最后,session1在插入新行時(shí)又得等待session2,故死鎖發(fā)生了。

        這種一般是在業(yè)務(wù)需求中基本不會(huì)出現(xiàn),因?yàn)槟沔i住了id=9,卻又想插入id=7的行,這就有點(diǎn)跳了,當(dāng)然肯定也有解決的方法,那就是重理業(yè)務(wù)需求,避免這樣的寫法。

        案例四

        圖片

        一般的情況,兩個(gè)session分別通過一個(gè)sql持有一把鎖,然后互相訪問對(duì)方加鎖的數(shù)據(jù)產(chǎn)生死鎖。

        案例五

        圖片

        兩個(gè)單條的sql語句涉及到的加鎖數(shù)據(jù)相同,但是加鎖順序不同,導(dǎo)致了死鎖。

        案例六

        死鎖場(chǎng)景如下:

        CREATE?TABLE?dltask?(
        ????????????????????????id?bigint?unsigned?NOT?NULL?AUTO_INCREMENT?COMMENT?‘auto?id’,
        ????????????????????????a?varchar(30)?NOT?NULL?COMMENT?‘uniq.a’,
        ????????????????????????b?varchar(30)?NOT?NULL?COMMENT?‘uniq.b’,
        ????????????????????????c?varchar(30)?NOT?NULL?COMMENT?‘uniq.c’,
        ????????????????????????x?varchar(30)?NOT?NULL?COMMENT?‘data’,
        ????????????????????????PRIMARY?KEY?(id),
        ????????????????????????UNIQUE?KEY?uniq_a_b_c?(a,?b,?c)
        )?ENGINE=InnoDB?DEFAULT?CHARSET=utf8?COMMENT=’deadlock?test’;

        a,b,c三列,組合成一個(gè)唯一索引 ,主鍵索引為id列。

        事務(wù)隔離級(jí)別:

        RR (Repeatable Read)

        每個(gè)事務(wù)只有一條SQL :

        delete?from?dltask?where?a=??and?b=??and?c=?;

        SQL的執(zhí)行計(jì)劃

        圖片

        死鎖日志

        圖片

        眾所周知,InnoDB上刪除一條記錄,并不是真正意義上的物理刪除,而是將記錄標(biāo)識(shí)為刪除狀態(tài)。(注:這些標(biāo)識(shí)為刪除狀態(tài)的記錄,后續(xù)會(huì)由后臺(tái)的Purge操作進(jìn)行回收,物理刪除。但是,刪除狀態(tài)的記錄會(huì)在索引中存放一段時(shí)間。) 在RR隔離級(jí)別下,唯一索引上滿足查詢條件,但是卻是刪除記錄,如何加鎖?InnoDB在此處的處理策略與前兩種策略均不相同,或者說是前兩種策略的組合:對(duì)于滿足條件的刪除記錄,InnoDB會(huì)在記錄上加next key lock X(對(duì)記錄本身加X鎖,同時(shí)鎖住記錄前的GAP,防止新的滿足條件的記錄插入。) Unique查詢,三種情況,對(duì)應(yīng)三種加鎖策略,總結(jié)如下:

        此處,我們看到了next key鎖,是否很眼熟?對(duì)了,前面死鎖中事務(wù)1,事務(wù)2處于等待狀態(tài)的鎖,均為next key鎖。明白了這三個(gè)加鎖策略,其實(shí)構(gòu)造一定的并發(fā)場(chǎng)景,死鎖的原因已經(jīng)呼之欲出。但是,還有一個(gè)前提策略需要介紹,那就是InnoDB內(nèi)部采用的死鎖預(yù)防策略。

        • 找到滿足條件的記錄,并且記錄有效,則對(duì)記錄加X鎖,No Gap鎖(lock_mode X locks rec but not gap);
        • 找到滿足條件的記錄,但是記錄無效(標(biāo)識(shí)為刪除的記錄),則對(duì)記錄加next key鎖(同時(shí)鎖住記錄本身,以及記錄之前的Gap:lock_mode X);
        • 未找到滿足條件的記錄,則對(duì)第一個(gè)不滿足條件的記錄加Gap鎖,保證沒有滿足條件的記錄插入(locks gap before rec);

        死鎖預(yù)防策略

        InnoDB引擎內(nèi)部(或者說是所有的數(shù)據(jù)庫內(nèi)部),有多種鎖類型:事務(wù)鎖(行鎖、表鎖),Mutex(保護(hù)內(nèi)部的共享變量操作)、RWLock(又稱之為L(zhǎng)atch,保護(hù)內(nèi)部的頁面讀取與修改)。

        InnoDB每個(gè)頁面為16K,讀取一個(gè)頁面時(shí),需要對(duì)頁面加S鎖,更新一個(gè)頁面時(shí),需要對(duì)頁面加上X鎖。任何情況下,操作一個(gè)頁面,都會(huì)對(duì)頁面加鎖,頁面鎖加上之后,頁面內(nèi)存儲(chǔ)的索引記錄才不會(huì)被并發(fā)修改。

        因此,為了修改一條記錄,InnoDB內(nèi)部如何處理:
        • 根據(jù)給定的查詢條件,找到對(duì)應(yīng)的記錄所在頁面;
        • 對(duì)頁面加上X鎖(RWLock),然后在頁面內(nèi)尋找滿足條件的記錄;
        • 在持有頁面鎖的情況下,對(duì)滿足條件的記錄加事務(wù)鎖(行鎖:根據(jù)記錄是否滿足查詢條件,記錄是否已經(jīng)被刪除,分別對(duì)應(yīng)于上面提到的3種加鎖策略之一);

        死鎖預(yù)防策略:相對(duì)于事務(wù)鎖,頁面鎖是一個(gè)短期持有的鎖,而事務(wù)鎖(行鎖、表鎖)是長(zhǎng)期持有的鎖。因此,為了防止頁面鎖與事務(wù)鎖之間產(chǎn)生死鎖。InnoDB做了死鎖預(yù)防的策略:持有事務(wù)鎖(行鎖、表鎖),可以等待獲取頁面鎖;但反之,持有頁面鎖,不能等待持有事務(wù)鎖。

        根據(jù)死鎖預(yù)防策略,在持有頁面鎖,加行鎖的時(shí)候,如果行鎖需要等待。則釋放頁面鎖,然后等待行鎖。此時(shí),行鎖獲取沒有任何鎖保護(hù),因此加上行鎖之后,記錄可能已經(jīng)被并發(fā)修改。因此,此時(shí)要重新加回頁面鎖,重新判斷記錄的狀態(tài),重新在頁面鎖的保護(hù)下,對(duì)記錄加鎖。如果此時(shí)記錄未被并發(fā)修改,那么第二次加鎖能夠很快完成,因?yàn)橐呀?jīng)持有了相同模式的鎖。但是,如果記錄已經(jīng)被并發(fā)修改,那么,就有可能導(dǎo)致本文前面提到的死鎖問題。

        以上的InnoDB死鎖預(yù)防處理邏輯,對(duì)應(yīng)的函數(shù),是row0sel.c::row_search_for_mysql()。感興趣的朋友,可以跟蹤調(diào)試下這個(gè)函數(shù)的處理流程,很復(fù)雜,但是集中了InnoDB的精髓。

        剖析死鎖的成因

        做了這么多鋪墊,有了Delete操作的3種加鎖邏輯、InnoDB的死鎖預(yù)防策略等準(zhǔn)備知識(shí)之后,再回過頭來分析本文最初提到的死鎖問題,就會(huì)手到拈來,事半而功倍。

        首先,假設(shè)dltask中只有一條記錄:(1, ‘a(chǎn)’, ‘b’, ‘c’, ‘data’)。三個(gè)并發(fā)事務(wù),同時(shí)執(zhí)行以下的這條SQL:

        delete?from?dltask?where?a=’a’?and?b=’b’?and?c=’c’;

        并且產(chǎn)生了以下的并發(fā)執(zhí)行邏輯,就會(huì)產(chǎn)生死鎖:

        圖片

        上面分析的這個(gè)并發(fā)流程,完整展現(xiàn)了死鎖日志中的死鎖產(chǎn)生的原因。其實(shí),根據(jù)事務(wù)1步驟6,與事務(wù)0步驟3/4之間的順序不同,死鎖日志中還有可能產(chǎn)生另外一種情況,那就是事務(wù)1等待的鎖模式為記錄上的X鎖 + No Gap鎖(lock_mode X locks rec but not gap waiting)。這第二種情況,也是”潤(rùn)潔”同學(xué)給出的死鎖用例中,使用MySQL 5.6.15版本測(cè)試出來的死鎖產(chǎn)生的原因。

        此類死鎖,產(chǎn)生的幾個(gè)前提:

        • Delete操作,針對(duì)的是唯一索引上的等值查詢的刪除;(范圍下的刪除,也會(huì)產(chǎn)生死鎖,但是死鎖的場(chǎng)景,跟本文分析的場(chǎng)景,有所不同)
        • 至少有3個(gè)(或以上)的并發(fā)刪除操作;
        • 并發(fā)刪除操作,有可能刪除到同一條記錄,并且保證刪除的記錄一定存在;
        • 事務(wù)的隔離級(jí)別設(shè)置為Repeatable Read,同時(shí)未設(shè)置innodb_locks_unsafe_for_binlog參數(shù)(此參數(shù)默認(rèn)為FALSE);(Read Committed隔離級(jí)別,由于不會(huì)加Gap鎖,不會(huì)有next key,因此也不會(huì)產(chǎn)生死鎖)
        • 使用的是InnoDB存儲(chǔ)引擎;(廢話!MyISAM引擎根本就沒有行鎖)

        參考

        https://blog.csdn.net/mine_song/article/details/71106410

        http://www.cnblogs.com/sessionbest/articles/8689082.html


        程序汪資料鏈接

        程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

        Java項(xiàng)目分享 最新整理全集,找項(xiàng)目不累啦 06版

        堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階

        臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開放下載!

        臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

        字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開放下載!


        歡迎添加程序汪個(gè)人微信 itwang005? 進(jìn)粉絲群或圍觀朋友圈

        瀏覽 59
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            污污网站在线观看 | 国产欧美第一页 | ass高耸美乳裸体pics | freehdxxxxchina麻豆 | 99久久免费精品国产在热线观 | 日韩精品成人一区二区在线观看 | 高清色| 狠狠色最新网址 | 成人免费无码区色情免费看 | 精品 熟女 国产 探花 AV |