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

          幻讀是啥,會有什么問題?如何解決?

          共 3633字,需瀏覽 8分鐘

           ·

          2021-09-21 13:53

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

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

          概念

          可重復讀

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

          提交讀

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

          案例

          幻讀是什么

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

          • sessionA首先開啟了一個事務(wù)并且在T1時刻給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é)束,如果同一個數(shù)據(jù)看到不同的結(jié)果。我們就稱為幻讀。

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

          幻讀有什么問題

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

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

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

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

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

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

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

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

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

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

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

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

          • sessionC在T4時刻,執(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*/

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

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

          由于 session A 把所有的行都加了寫鎖,所以 session B 在執(zhí)行第一個 update 語句的時候就被鎖住了。需要等到 T6 時刻 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ù)值也是對的了。那么還有一個問題!

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

          如何解決幻讀?間隙鎖!

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

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

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

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

          行鎖分為:讀鎖,寫鎖。

          間隙鎖是單獨的一個鎖。

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

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

          舉例說明一下

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

          • session B 并不會被堵住。因為表 t 里并沒有 c=7 這個記錄,因此 session A 加的是間隙鎖 (5,10)。而 session B 也是在這個間隙加的間隙鎖。它們有共同的目標,即:保護這個間隙,不允許插入值。但,它們之間是不沖突的。

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

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

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

          回到案例

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

          我們先引一個邏輯出來繼續(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;

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

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

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

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

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

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

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

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

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

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

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

          總結(jié)

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


          瀏覽 87
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产成人无码AⅤ片免费播放 | 大香蕉太香蕉成人现现 | 青娱乐在线视频网站 | 久久婷婷五月天综合 | 又大又粗免费视频 |