<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>

          這六個(gè) MySQL 死鎖案例,能讓你理解死鎖的原因!

          共 8087字,需瀏覽 17分鐘

           ·

          2021-05-05 11:50

          公眾號(hào)關(guān)注“杰哥的IT之旅”,
          選擇“星標(biāo)”,重磅干貨,第一時(shí)間送達(dá)!

          來(lái)自:https://blog.csdn.net/a_blackmoon/article/details/106983859

          最近總結(jié)了一波死鎖問(wèn)題,和大家分享一下,我這也是從網(wǎng)上各種瀏覽博客得來(lái)。

          Mysql 鎖類型和加鎖分析

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

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


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


          頁(yè)面鎖:開銷和加鎖時(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)生原因

          所謂死鎖<DeadLock>:是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,若無(wú)外力作用,它們都將無(wú)法推進(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)的解決死鎖問(wèn)題的關(guān)鍵就是:讓不同的session加鎖有次序

          產(chǎn)生示例

          案例一

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

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

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

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

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

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

          對(duì)于這個(gè)問(wèn)題的改進(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,9for 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,5for update;
          鎖等待中……

          其實(shí)這個(gè)時(shí)候id=10這條記錄沒(méi)有被鎖住的,但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)
          在其它session中id=5是加不了鎖的,但是id=10是可以加上鎖的。
          案例二

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

          以id為主鍵為例,目前還沒(méi)有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鎖)

          鎖住的范圍為:

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

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

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

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

          那么就鎖住(11,30)

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

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

          用mysql特有的語(yǔ)法來(lái)解決此問(wèn)題。因?yàn)閕nsert語(yǔ)句對(duì)于主鍵來(lái)說(shuō),插入的行不管有沒(méi)有存在,都會(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的范圍并沒(méi)有被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分別通過(guò)一個(gè)sql持有一把鎖,然后互相訪問(wèn)對(duì)方加鎖的數(shù)據(jù)產(chǎn)生死鎖。

          案例五

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

          案例六

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

          CREATE TABLE dltask (
              id bigint unsigned NOT NULL AUTO_INCREMENT COMMENT ‘auto id’,
              a varchar(30NOT NULL COMMENT ‘uniq.a’,
              b varchar(30NOT NULL COMMENT ‘uniq.b’,
              c varchar(30NOT NULL COMMENT ‘uniq.c’,
              x varchar(30NOT 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在此處的處理策略與前兩種策略均不相同,或者說(shuō)是前兩種策略的組合:對(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);

          • 找到滿足條件的記錄,但是記錄無(wú)效(標(biāo)識(shí)為刪除的記錄),則對(duì)記錄加next key鎖(同時(shí)鎖住記錄本身,以及記錄之前的Gap:lock_mode X);

          • 未找到滿足條件的記錄,則對(duì)第一個(gè)不滿足條件的記錄加Gap鎖,保證沒(méi)有滿足條件的記錄插入(locks gap before rec);

          死鎖預(yù)防策略

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

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

          因此,為了修改一條記錄,InnoDB內(nèi)部如何處理:
          • 根據(jù)給定的查詢條件,找到對(duì)應(yīng)的記錄所在頁(yè)面;

          • 對(duì)頁(yè)面加上X鎖(RWLock),然后在頁(yè)面內(nèi)尋找滿足條件的記錄;

          • 在持有頁(yè)面鎖的情況下,對(duì)滿足條件的記錄加事務(wù)鎖(行鎖:根據(jù)記錄是否滿足查詢條件,記錄是否已經(jīng)被刪除,分別對(duì)應(yīng)于上面提到的3種加鎖策略之一);

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

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

          以上的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í)之后,再回過(guò)頭來(lái)分析本文最初提到的死鎖問(wèn)題,就會(huì)手到拈來(lái),事半而功倍。

          首先,假設(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è)試出來(lái)的死鎖產(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引擎根本就沒(méi)有行鎖)

          參考

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

          • http://hedengcheng.com/?p=844

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

          推薦閱讀

          學(xué)習(xí) MySQL 高性能優(yōu)化原理,這一篇就夠了!

          簡(jiǎn)單、易用的 MySQL 官方壓測(cè)工具,建議收藏!

          13000字!最常問(wèn)的MySQL面試題集合

          MySQL 常用優(yōu)化指南,及大表優(yōu)化思路都在這了!

          超全面的 MySQL 優(yōu)化面試解析

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  日本高清在线一区 | 欧美在线视频91 | 色老板在线精品免费观看 | 亚洲国产另类无码日韩 | 国产乱伦大杂烩 |