1. 面試官:怎么解決MySQL中的死鎖問題?

        共 4913字,需瀏覽 10分鐘

         ·

        2021-09-20 09:14

        咱們使用 MySQL 大概率上都會遇到死鎖問題,這實(shí)在是個令人非常頭痛的問題。本文將會對死鎖進(jìn)行相應(yīng)介紹,對常見的死鎖案例進(jìn)行相關(guān)分析與探討,以及如何去盡可能避免死鎖給出一些建議。

        話不多說,開整!

        什么是死鎖

        死鎖是并發(fā)系統(tǒng)中常見的問題,同樣也會出現(xiàn)在數(shù)據(jù)庫MySQL的并發(fā)讀寫請求場景中。當(dāng)兩個及以上的事務(wù),雙方都在等待對方釋放已經(jīng)持有的鎖或因?yàn)榧渔i順序不一致造成循環(huán)等待鎖資源,就會出現(xiàn)“死鎖”。常見的報錯信息為 Deadlock found when trying to get lock...。

        舉例來說 A 事務(wù)持有 X1 鎖 ,申請 X2 鎖,B事務(wù)持有 X2 鎖,申請 X1 鎖。A 和 B 事務(wù)持有鎖并且申請對方持有的鎖進(jìn)入循環(huán)等待,就造成了死鎖。

        如上圖,是右側(cè)的四輛汽車資源請求產(chǎn)生了回路現(xiàn)象,即死循環(huán),導(dǎo)致了死鎖。

        從死鎖的定義來看,MySQL 出現(xiàn)死鎖的幾個要素為:

        1. 兩個或者兩個以上事務(wù)
        2. 每個事務(wù)都已經(jīng)持有鎖并且申請新的鎖
        3. 鎖資源同時只能被同一個事務(wù)持有或者不兼容
        4. 事務(wù)之間因?yàn)槌钟墟i和申請鎖導(dǎo)致彼此循環(huán)等待

        InnoDB 鎖類型

        為了分析死鎖,我們有必要對 InnoDB 的鎖類型有一個了解。

        MySQL InnoDB 引擎實(shí)現(xiàn)了標(biāo)準(zhǔn)的行級別鎖:共享鎖( S lock ) 和排他鎖 ( X lock )

        1. 不同事務(wù)可以同時對同一行記錄加 S 鎖。
        2. 如果一個事務(wù)對某一行記錄加 X 鎖,其他事務(wù)就不能加 S 鎖或者 X 鎖,從而導(dǎo)致鎖等待。

        如果事務(wù) T1 持有行 r 的 S 鎖,那么另一個事務(wù) T2 請求 r 的鎖時,會做如下處理:

        1. T2 請求 S 鎖立即被允許,結(jié)果 T1 T2 都持有 r 行的 S 鎖
        2. T2 請求 X 鎖不能被立即允許

        如果 T1 持有 r 的 X 鎖,那么 T2 請求 r 的 X、S 鎖都不能被立即允許,T2 必須等待 T1 釋放 X 鎖才可以,因?yàn)?X 鎖與任何的鎖都不兼容。共享鎖和排他鎖的兼容性如下所示:

        間隙鎖( gap lock )

        間隙鎖鎖住一個間隙以防止插入。假設(shè)索引列有2, 4, 8 三個值,如果對 4 加鎖,那么也會同時對(2,4)和(4,8)這兩個間隙加鎖。其他事務(wù)無法插入索引值在這兩個間隙之間的記錄。但是,間隙鎖有個例外:

        1. 如果索引列是唯一索引,那么只會鎖住這條記錄(只加行鎖),而不會鎖住間隙。
        2. 對于聯(lián)合索引且是唯一索引,如果 where 條件只包括聯(lián)合索引的一部分,那么依然會加間隙鎖。

        next-key lock

        next-key lock 實(shí)際上就是 行鎖+這條記錄前面的 gap lock 的組合。假設(shè)有索引值10,11,13和 20,那么可能的 next-key lock 包括:

        (負(fù)無窮,10],(10,11],(11,13],(13,20],(20,正無窮)

        在 RR 隔離級別下,InnoDB 使用 next-key lock 主要是防止幻讀問題產(chǎn)生。

        意向鎖( Intention lock )

        InnoDB 為了支持多粒度的加鎖,允許行鎖和表鎖同時存在。為了支持在不同粒度上的加鎖操作,InnoDB 支持了額外的一種鎖方式,稱之為意向鎖( Intention Lock )。意向鎖是將鎖定的對象分為多個層次,意向鎖意味著事務(wù)希望在更細(xì)粒度上進(jìn)行加鎖。意向鎖分為兩種:

        1. 意向共享鎖( IS ):事務(wù)有意向?qū)Ρ碇械哪承┬屑庸蚕礞i
        2. 意向排他鎖( IX ):事務(wù)有意向?qū)Ρ碇械哪承┬屑优潘i

        由于 InnoDB 存儲引擎支持的是行級別的鎖,因此意向鎖其實(shí)不會阻塞除全表掃描以外的任何請求。表級意向鎖與行級鎖的兼容性如下所示:

        插入意向鎖( Insert Intention lock )

        插入意向鎖是在插入一行記錄操作之前設(shè)置的一種間隙鎖,這個鎖釋放了一種插入方式的信號,即多個事務(wù)在相同的索引間隙插入時如果不是插入間隙中相同的位置就不需要互相等待。假設(shè)某列有索引值2,6,只要兩個事務(wù)插入位置不同(如事務(wù) A 插入3,事務(wù) B 插入4),那么就可以同時插入。

        鎖模式兼容矩陣

        橫向是已持有鎖,縱向是正在請求的鎖:

        閱讀死鎖日志

        在進(jìn)行具體案例分析之前,咱們先了解下如何去讀懂死鎖日志,盡可能地使用死鎖日志里面的信息來幫助我們來解決死鎖問題。

        后面測試用例的數(shù)據(jù)庫場景如下:MySQL 5.7 事務(wù)隔離級別為 RR

        表結(jié)構(gòu)和數(shù)據(jù)如下:

        測試用例如下:

        通過執(zhí)行show engine innodb status 可以查看到最近一次死鎖的日志。

        日志分析如下:

        1. ***** (1) TRANSACTION: TRANSACTION 2322, ACTIVE 6 sec starting index read

        事務(wù)號為2322,活躍 6秒,starting index read 表示事務(wù)狀態(tài)為根據(jù)索引讀取數(shù)據(jù)。常見的其他狀態(tài)有:

        mysql tables in use 1 說明當(dāng)前的事務(wù)使用一個表。

        locked 1 表示表上有一個表鎖,對于 DML 語句為 LOCK_IX

        LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)

        LOCK WAIT 表示正在等待鎖,2 lock struct(s) 表示 trx->trx_locks 鎖鏈表的長度為2,每個鏈表節(jié)點(diǎn)代表該事務(wù)持有的一個鎖結(jié)構(gòu),包括表鎖,記錄鎖以及自增鎖等。本用例中 2locks 表示 IX 鎖和lock_mode X (Next-key lock)

        1 row lock(s) 表示當(dāng)前事務(wù)持有的行記錄鎖/ gap 鎖的個數(shù)。

        MySQL thread id 37, OS thread handle 140445500716800, query id 1234 127.0.0.1 root updating

        MySQL thread id 37 表示執(zhí)行該事務(wù)的線程 ID 為 37 (即 show processlist; 展示的 ID )

        delete from student where stuno=5 表示事務(wù)1正在執(zhí)行的 sql,比較難受的事情是 show engine innodb status 是查看不到完整的 sql 的,通常顯示當(dāng)前正在等待鎖的 sql。

        ***** (1) WAITING FOR THIS LOCK TO BE GRANTED:

        RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2322 lock_mode X waiting

        RECORD LOCKS 表示記錄鎖, 此條內(nèi)容表示事務(wù) 1 正在等待表 student 上的 idx_stuno 的 X 鎖,本案例中其實(shí)是 Next-Key Lock 。

        事務(wù)2的 log 和上面分析類似:

        1. ***** (2) HOLDS THE LOCK(S):

        RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2321 lock_mode X

        顯示事務(wù) 2 的 insert into student(stuno,score) values(2,10) 持有了 a=5 的 Lock mode X

        | LOCK_gap,不過我們從日志里面看不到事務(wù)2執(zhí)行的 delete from student where stuno=5;

        這點(diǎn)也是造成 DBA 僅僅根據(jù)日志難以分析死鎖的問題的根本原因。

        1. ***** (2) WAITING FOR THIS LOCK TO BE GRANTED:

        RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2321 lock_mode X locks gap before rec insert intention waiting

        表示事務(wù) 2 的 insert 語句正在等待插入意向鎖 lock_mode X locks gap before rec insert intention waiting ( LOCK_X + LOCK_REC_gap )

        經(jīng)典案例分析

        案例一:事務(wù)并發(fā) insert 唯一鍵沖突

        表結(jié)構(gòu)和數(shù)據(jù)如下所示:測試用例如下:日志分析如下:

        1. 事務(wù) T2 insert into t7(id,a) values (26,10) 語句 insert 成功,持有 a=10 的 排他行鎖( Xlocks rec but no gap )
        2. 事務(wù) T1 insert into t7(id,a) values (30,10), 因?yàn)門2的第一條 insert 已經(jīng)插入 a=10 的記錄,事務(wù) T1 insert a=10 則發(fā)生唯一鍵沖突,需要申請對沖突的唯一索引加上S Next-key Lock( 即 lock mode S waiting ) 這是一個間隙鎖會申請鎖住(,10],(10,20]之間的 gap 區(qū)域。
        3. 事務(wù) T2 insert into t7(id,a) values (40,9)該語句插入的 a=9 的值在事務(wù) T1 申請的 gap 鎖4-10之間, 故需事務(wù) T2 的第二條 insert 語句要等待事務(wù) T1 的 S-Next-key Lock 鎖釋放,在日志中顯示 lock_mode X locks gap before rec insert intention waiting 。

        案例一:先 update 再 insert 的并發(fā)死鎖問題

        表結(jié)構(gòu)如下,無數(shù)據(jù):測試用例如下:死鎖分析:
        可以看到兩個事務(wù) update 不存在的記錄,先后獲得間隙鎖( gap 鎖),gap 鎖之間是兼容的所以在update環(huán)節(jié)不會阻塞。兩者都持有 gap 鎖,然后去競爭插入意向鎖。當(dāng)存在其他會話持有 gap 鎖的時候,當(dāng)前會話申請不了插入意向鎖,導(dǎo)致死鎖。

        如何盡可能避免死鎖

        1. 合理的設(shè)計(jì)索引,區(qū)分度高的列放到組合索引前面,使業(yè)務(wù) SQL 盡可能通過索引定位更少的行,減少鎖競爭。
        2. 調(diào)整業(yè)務(wù)邏輯 SQL 執(zhí)行順序, 避免 update/delete 長時間持有鎖的 SQL 在事務(wù)前面。
        3. 避免大事務(wù),盡量將大事務(wù)拆成多個小事務(wù)來處理,小事務(wù)發(fā)生鎖沖突的幾率也更小。
        4. 固定的順序訪問表和行。比如兩個更新數(shù)據(jù)的事務(wù),事務(wù) A 更新數(shù)據(jù)的順序?yàn)?1,2;事務(wù) B 更新數(shù)據(jù)的順序?yàn)?2,1。這樣更可能會造成死鎖。
        5. 在并發(fā)比較高的系統(tǒng)中,不要顯式加鎖,特別是是在事務(wù)里顯式加鎖。如 select … for update 語句,如果是在事務(wù)里(運(yùn)行了 start transaction 或設(shè)置了autocommit 等于0),那么就會鎖定所查找到的記錄。
        6. 盡量按主鍵/索引去查找記錄,范圍查找增加了鎖沖突的可能性,也不要利用數(shù)據(jù)庫做一些額外額度計(jì)算工作。比如有的程序會用到 “select … where … order by rand();”這樣的語句,由于類似這樣的語句用不到索引,因此將導(dǎo)致整個表的數(shù)據(jù)都被鎖住。
        7. 優(yōu)化 SQL 和表設(shè)計(jì),減少同時占用太多資源的情況。比如說,減少連接的表,將復(fù)雜 SQL 分解為多個簡單的 SQL。

        好了。今天就說到這了,我還會不斷分享自己的所學(xué)所想,希望我們一起走在成功的道路上!


        往期推薦

        1.3w字,一文詳解死鎖!


        10個經(jīng)典又容易被人疏忽的JVM面試題


        什么是可中斷鎖?有什么用?怎么實(shí)現(xiàn)?





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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報
          
          

            1. 加勒比色老久久爱综合网 | 美国式禁忌14观看中文 | 黑丝自慰网站 | 久操国产 | 成人视频无码在线观看 |