1. 幻讀是啥,會(huì)有什么問題?如何解決?

        共 3633字,需瀏覽 8分鐘

         ·

        2021-09-21 13:53

        微信公眾號:歡少的成長之路,來個(gè) 點(diǎn)贊,轉(zhuǎn)發(fā),在看!

        大家好,我是Leo,上篇文章大概介紹了為什么查詢一條記錄性能慢的原因。今天我們介紹一下幻讀的一些相關(guān)知識(shí),以及幻讀相關(guān)的間隙鎖,間隙鎖死鎖的解決方案。

        概念

        可重復(fù)讀

        兩個(gè)事務(wù)進(jìn)行數(shù)據(jù)操作他們是互不干擾的 ,事務(wù)先A進(jìn)行數(shù)據(jù)查詢,事務(wù)B進(jìn)行一次事務(wù)修改并進(jìn)行數(shù)據(jù)提交,事務(wù)A再進(jìn)行一次查詢,數(shù)據(jù)是不改變的

        提交讀

        兩個(gè)事務(wù)進(jìn)行數(shù)據(jù)操作,事務(wù)先A進(jìn)行數(shù)據(jù)查詢,事務(wù)B進(jìn)行一次事務(wù)修改并進(jìn)行數(shù)據(jù)提交,事務(wù)A再進(jìn)行一次查詢,數(shù)據(jù)是B修改后的數(shù)據(jù)。

        案例

        幻讀是什么

        如下圖所示,我們一起分析一下。

        • sessionA首先開啟了一個(gè)事務(wù)并且在T1時(shí)刻給d為5的數(shù)據(jù)加上了寫鎖

        • sessionB沒有開啟事務(wù)。修改了id為0的數(shù)據(jù),把d改成了5

        • sessionA繼續(xù)執(zhí)行了d=5的數(shù)據(jù)加上了寫鎖

        • sessionC插入了一條數(shù)據(jù)115

        • sessionA再次查詢數(shù)據(jù)就發(fā)現(xiàn)數(shù)據(jù)一直在變,一直在多

        這種從事務(wù)開啟到事務(wù)結(jié)束,如果同一個(gè)數(shù)據(jù)看到不同的結(jié)果。我們就稱為幻讀。

        for update 加了寫鎖都是 當(dāng)前讀。而當(dāng)前讀的規(guī)則就是看到所有已經(jīng)提交過的數(shù)據(jù)。

        幻讀有什么問題

        如下圖所示,我們繼續(xù)分析一下

        session B 的第二條語句 update t set c=5 where id=0,語義是“我把 id=0、d=5 這一行的 c 值,改成了 5”。

        由于在 T1 時(shí)刻,session A 還只是給 id=5 這一行加了行鎖, 并沒有給 id=0 這行加上鎖。因此,session B 在 T2 時(shí)刻,是可以執(zhí)行這兩條 update 語句的。這樣,就破壞了 session A 里 Q1 語句要鎖住所有 d=5 的行的加鎖聲明。

        session C 也是一樣的道理,對 id=1 這一行的修改,也是破壞了 Q1 的加鎖聲明。

        以上是語義上的問題。下面還有數(shù)據(jù)一致性上的問題

        我們知道,鎖的設(shè)計(jì)是為了保證數(shù)據(jù)的一致性。而這個(gè)一致性,不止是數(shù)據(jù)庫內(nèi)部數(shù)據(jù)狀態(tài)在此刻的一致性,還包含了數(shù)據(jù)和日志在邏輯上的一致性。

        如下圖,我們繼續(xù)分析會(huì)有什么問題。

        為了說明這個(gè)問題,我給 session A 在 T1 時(shí)刻再加一個(gè)更新語句,即:update t set d=100 where d=5。

        update跟for update的含義是一樣的。都是給d為5的數(shù)據(jù)加鎖。然后修改成d為100

        • sessionA在T1時(shí)刻,會(huì)給d為5的數(shù)據(jù)加鎖。并且修改d為100(不提交

        • sessionB在T2時(shí)刻,會(huì)修改id為0的數(shù)據(jù)改成d,c為5。(提交

        • 回到了sessionA的T3時(shí)刻,再次查詢加寫鎖

        • sessionC在T4時(shí)刻,執(zhí)行了插入語句,修改id為1的數(shù)據(jù)c為5.(提交

        這樣看好像也沒啥邏輯和一致性問題。再來看一下binlog日志

        update t set d=5 where id=0; /*(0,0,5)*/
        update t set c=5 where id=0; /*(0,5,5)*/

        insert into t values(1,1,5); /*(1,1,5)*/
        update t set c=5 where id=1; /*(1,5,5)*/

        update t set d=100 where d=5;/*所有d=5的行,d改成100*/

        你會(huì)發(fā)現(xiàn)在執(zhí)行這三行結(jié)果都變成了(0,5,100)、(1,5,100) 和 (5,5,100)。也就說有兩條數(shù)據(jù)被改了。

        那么我們應(yīng)該怎么改?如下圖,加了鎖的

        由于 session A 把所有的行都加了寫鎖,所以 session B 在執(zhí)行第一個(gè) update 語句的時(shí)候就被鎖住了。需要等到 T6 時(shí)刻 session A 提交以后,session B 才能繼續(xù)執(zhí)行。

        這樣對于 id=0 這一行,在數(shù)據(jù)庫里的最終結(jié)果還是 (0,5,5)。在 binlog 里面,執(zhí)行序列是這樣的:

        insert into t values(1,1,5); /*(1,1,5)*/
        update t set c=5 where id=1; /*(1,5,5)*/

        update t set d=100 where d=5;/*所有d=5的行,d改成100*/

        update t set d=5 where id=0; /*(0,0,5)*/
        update t set c=5 where id=0; /*(0,5,5)*/

        上圖的binlog數(shù)據(jù)不一致的問題算是解決了。數(shù)值也是對的了。那么還有一個(gè)問題!

        全部加鎖解決了每個(gè)數(shù)據(jù)的正確性,那么新數(shù)據(jù)就無法保證正確性了?,F(xiàn)在就不是讀寫鎖可以解決的了。

        如何解決幻讀?間隙鎖!

        今天我們聊一下間隙鎖。簡單介紹一下。比如一個(gè)表中有6行數(shù)據(jù)。那么就會(huì)加7個(gè)間隙鎖。這7個(gè)鎖就分布在每條記錄的前后。

        當(dāng)你執(zhí)行 select * from t where d=5 for update 的時(shí)候。就不止是給數(shù)據(jù)庫中已有的 6 個(gè)記錄加上了行鎖,還同時(shí)加了 7 個(gè)間隙鎖。這樣就確保了無法再插入新的記錄。

        數(shù)據(jù)行是可以加上鎖的實(shí)體,數(shù)據(jù)行之間的間隙,也是可以加上鎖的實(shí)體。

        行鎖,間隙鎖,讀鎖,寫鎖

        行鎖分為:讀鎖,寫鎖。

        間隙鎖是單獨(dú)的一個(gè)鎖。

        也就是說,跟行鎖有沖突關(guān)系的是“另外一個(gè)行鎖”。

        跟間隙鎖存在沖突關(guān)系的,是“往這個(gè)間隙中插入一個(gè)記錄”這個(gè)操作。間隙鎖之間都不存在沖突關(guān)系。

        舉例說明一下

        • sessionA開啟一個(gè)事務(wù) 并且給c為7的數(shù)據(jù)加了一個(gè)讀鎖。

        • session B 并不會(huì)被堵住。因?yàn)楸?t 里并沒有 c=7 這個(gè)記錄,因此 session A 加的是間隙鎖 (5,10)。而 session B 也是在這個(gè)間隙加的間隙鎖。它們有共同的目標(biāo),即:保護(hù)這個(gè)間隙,不允許插入值。但,它們之間是不沖突的。

        間隙鎖和行鎖合稱 next-key lock,每個(gè) next-key lock 是前開后閉區(qū)間

        那么我們在使用for update的時(shí)候也就是加了7 個(gè) next-key lock,分別是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。

        supremum:因?yàn)?+∞是開區(qū)間。實(shí)現(xiàn)上,InnoDB 給每個(gè)索引加了一個(gè)不存在的最大值 supremum,這樣才符合我們前面說的“都是前開后閉區(qū)間”。

        回到案例

        間隙鎖和 next-key lock 的引入,幫我們解決了幻讀的問題,但同時(shí)也帶來了一些“困擾”。

        我們先引一個(gè)邏輯出來繼續(xù)理論!

        **業(yè)務(wù)邏輯 **是這樣的:任意鎖住一行,如果這一行不存在的話就插入,如果存在這一行就更新它的數(shù)據(jù)

        begin;
        select * from t where id=N for update;

        /*如果行不存在*/
        insert into t values(N,N,N);
        /*如果行存在*/
        update t set d=N set id=N;

        commit;

        這個(gè)邏輯一旦有并發(fā),就會(huì)碰到死鎖。你一定也覺得奇怪,這個(gè)邏輯每次操作前用 for update 鎖起來,已經(jīng)是最嚴(yán)格的模式了,怎么還會(huì)有死鎖呢?

        如下圖,假設(shè)N=9

        • session A 執(zhí)行 select … for update 語句,由于 id=9 這一行并不存在,因此會(huì)加上間隙鎖 (5,10);

        • session B 執(zhí)行 select … for update 語句,同樣會(huì)加上間隙鎖 (5,10),間隙鎖之間不會(huì)沖突,因此這個(gè)語句可以執(zhí)行成功;

        • session B 試圖插入一行 (9,9,9),被 session A 的間隙鎖擋住了,只好進(jìn)入等待;

        • session A 試圖插入一行 (9,9,9),被 session B 的間隙鎖擋住了。

        至此,兩個(gè) session 進(jìn)入互相等待狀態(tài),形成死鎖。當(dāng)然,InnoDB 的死鎖檢測馬上就發(fā)現(xiàn)了這對死鎖關(guān)系,讓 session A 的 insert 語句報(bào)錯(cuò)返回了。

        結(jié)論:間隙鎖的引入,可能會(huì)導(dǎo)致同樣的語句鎖住更大的范圍,這其實(shí)是影響了并發(fā)度的

        業(yè)務(wù)權(quán)衡

        一開始我們就提到了,幻讀只會(huì)出現(xiàn)在可重復(fù)隔離級別情況下。間隙鎖是在可重復(fù)讀隔離級別下才會(huì)生效的。

        所以,你如果把隔離級別設(shè)置為讀提交的話,就沒有間隙鎖了。但同時(shí),你要解決可能出現(xiàn)的數(shù)據(jù)和日志不一致問題,需要把 binlog 格式設(shè)置為 row。這,也是現(xiàn)在不少公司使用的配置組合。

        總結(jié)

        生產(chǎn)庫上會(huì)經(jīng)常出現(xiàn)由于間隙鎖導(dǎo)致的死鎖現(xiàn)象。行鎖確實(shí)比較直觀,判斷規(guī)則也相對簡單,間隙鎖的引入會(huì)影響系統(tǒng)的并發(fā)度,也增加了鎖分析的復(fù)雜度,但也有章可循


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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 亚洲天堂小说 | 国产伦精品一区二区三区妓国产 | 激情野战男女做爰 | 豆花视频网 | 一级无码视频黄片免费的 |