<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 死鎖的場景、原因及如何預(yù)防死鎖?

          共 6376字,需瀏覽 13分鐘

           ·

          2021-12-27 17:43

          Mysql 鎖類型和加鎖分析

          MySQL有三種鎖的級別:頁級、表級、行級。

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

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

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

          算法:

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

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

          產(chǎn)生原因

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

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

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

          產(chǎn)生示例

          案例一

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

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

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

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

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

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

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

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

          在in里面的列表值mysql是會自動從小到大排序,加鎖也是一條條從小到大加的鎖

          例如(以下會話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í)這個時候id=10這條記錄沒有被鎖住的,但id=5的記錄已經(jīng)被鎖住了,鎖的等待在id=8的這里
          不信請看

          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)常會做這類的判斷需求:根據(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)對存在的行進(jìn)行鎖的時候(主鍵),mysql就只有行鎖。當(dāng)對未存在的行進(jìn)行鎖的時候(即使條件為主鍵),mysql是會鎖住一段范圍(有g(shù)ap鎖)

          鎖住的范圍為:

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

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

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

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

          那么就鎖?。?1,30)

          對于這種死鎖的解決辦法是:

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

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

          案例三

          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

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

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

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

          案例四

          圖片

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

          案例五

          圖片

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

          案例六

          死鎖場景如下:

          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三列,組合成一個唯一索引 ,主鍵索引為id列。

          事務(wù)隔離級別:

          RR (Repeatable Read)

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

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

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

          圖片

          死鎖日志

          圖片

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

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

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

          死鎖預(yù)防策略

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

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

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

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

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

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

          剖析死鎖的成因

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

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

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

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

          圖片

          上面分析的這個并發(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)。這第二種情況,也是”潤潔”同學(xué)給出的死鎖用例中,使用MySQL 5.6.15版本測試出來的死鎖產(chǎn)生的原因。

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

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

          參考

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

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


          程序汪資料鏈接

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

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

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

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

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

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


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

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

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  操逼视频网站网址 | 国产精品亚洲专区在线播放麻豆 | 欧美高潮AAAAAA片 | 五月丁香婷婷综合 | 久久精品国产亚洲AV无码蜜芽 |