1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        一文講清,MySQL如何解決多事務(wù)并發(fā)問題

        共 3124字,需瀏覽 7分鐘

         ·

        2021-10-21 08:00

        MySQL默認(rèn)事務(wù)隔離級別是repeatable-read(RR),臟讀、不可重復(fù)讀、幻讀,都不會發(fā)生。它是怎么做到的呢?


        這就是由經(jīng)典的MVCC多版本并發(fā)控制機(jī)制做到的,MVCC的實現(xiàn),又是基于undo log版本鏈的。


        前面講MySQL一行數(shù)據(jù)的存儲格式,講到了每行數(shù)據(jù)有兩個隱藏的字段:trx_id、roll_pointer。trx_id就是最近一次更新這條數(shù)據(jù)的事務(wù)id,roll_pointer指向了你更新這個事務(wù)之前生成的undo log。


        假設(shè)有一個事務(wù)A(id = 50),插入了一條數(shù)據(jù)A,它的數(shù)據(jù)格式如下:

        圖1?undo log版本鏈


        接著事務(wù)B修改這條數(shù)據(jù)把值修改為B,事務(wù)B的id是58,此時會生成一個undo log記錄之前的值,roll_pointer指向這個undo log日志。

        圖2?undo log版本鏈


        假設(shè)再來了一個事務(wù)C,它的事務(wù)id是68,把數(shù)據(jù)值改為了C,此時undo log版本鏈就變成這樣了。


        圖3?undo log版本鏈


        事務(wù)執(zhí)行的時候,都會更新隱藏的字段trx_id和roll_pointer,同時之前多個數(shù)據(jù)快照對應(yīng)的undo log也會通過roll_pointer串聯(lián)起來,最終形成一個版本鏈。


        基于undo log實現(xiàn)的ReadView


        執(zhí)行一個事務(wù)的時候,會生成一個ReadView,里面包含這些東西:


        • m_ids,此時有哪些事務(wù)在MySQL中還沒有提交的事務(wù)id;

        • min_trx_id,m_ids里最小的;

        • max_trx_id,MySQL下一個要生成的事務(wù)id;

        • creator_trx_id,表示生成該ReadView的事務(wù)的事務(wù)id。


        假設(shè)數(shù)據(jù)庫中有一行數(shù)據(jù),值是A,事務(wù)id是32,如下圖所示:

        圖4 初始情況下,數(shù)據(jù)庫中有一行數(shù)據(jù)


        此時有兩個事務(wù)并發(fā)過來執(zhí)行,事務(wù)A(id=45),事務(wù)B(id=59),事務(wù)A要去讀取這行數(shù)據(jù),事務(wù)B要去修改這行數(shù)據(jù)。


        事務(wù)A開啟一個ReadView,此時它長這樣:

        圖5 ReadView


        ReadView的m_ids包含事務(wù)A和事務(wù)B的兩個id,45和49,min_trx_id是45,max_trx_id是60,creator_trx_id就是45,就是事務(wù)A自己。


        這時候事務(wù)A第一次查詢這行數(shù)據(jù),會去判斷一下當(dāng)前這行數(shù)據(jù)的trx_id是否小于ReadView中的min_trx_id?,F(xiàn)在trx_id = 32,是小于ReadView里的min_trx_id=45的,說明你事務(wù)開啟之前,修改這行數(shù)據(jù)的事務(wù)早就提交了,所以此時可以查詢到這行數(shù)據(jù)。

        圖6?事務(wù)A讀取數(shù)據(jù)


        接著事務(wù)B開始修改這行數(shù)據(jù),事務(wù)B把值修改為B,然后這行數(shù)據(jù)的trx_id設(shè)置為自己的id,也就是59,同時roll_pointer指向了修改之前生成的undo log。

        圖7?事務(wù)B修改數(shù)據(jù)


        這時候事務(wù)A第二次查詢,發(fā)現(xiàn)此時數(shù)據(jù)行里的trx_id=59,大于ReadView里的min_trx_id=45,同時小于max_trx_id=60,說明更新這條數(shù)據(jù)的事務(wù),很可能跟自己差不多同時開啟。果然ReadView的m_ids里有45和59兩個事務(wù)id,事務(wù)B是跟自己并發(fā)執(zhí)行提交的,所以這行數(shù)據(jù)是不能查詢的。

        圖8?事務(wù)A第二次讀數(shù)據(jù)


        事務(wù)A不能查修改后的值,那怎么辦?順著undo log版本鏈查詢之前的版本!


        于是就會查到trx_id=32的數(shù)據(jù),trx_id=32是小于ReadView里min_trx_id=45的,可以查出來。


        看到這里,大家能不能猜想到多事務(wù)并發(fā)的時候,MySQL是如何解決那一堆問題的?就是通過undo log版本鏈 + ReadView解決的!


        假設(shè)事務(wù)A執(zhí)行的過程中,事務(wù)C來更新這行數(shù)據(jù)為C,事務(wù)id=78。


        圖9 事務(wù)C修改數(shù)據(jù)


        此時事務(wù)A第三次去查,發(fā)現(xiàn)當(dāng)前數(shù)據(jù)的trx_id=78,比ReadView中的max_trx_id=60還大,說明這條數(shù)據(jù)是事務(wù)A開啟之后修改的,不應(yīng)該查到!


        于是事務(wù)A順著undo log版本鏈往下找,先找到trx_id=59的數(shù)據(jù),上面分析過了,這條數(shù)據(jù)也不能查,于是繼續(xù)向undo log版本鏈向下找,最終返回trx_id=32的數(shù)據(jù)。


        通過undo log版本鏈和ReadView,MySQL就可以保證你只能讀取到事務(wù)開啟前別的事務(wù)更新的值,和自己更新的值。


        總的來說,就是一個事務(wù)只能讀取到事務(wù)id小于等于自己的數(shù)據(jù)。


        讀已提交(RC)如何基于MVCC實現(xiàn)多事務(wù)并發(fā)控制?


        只要你搞明白了上面的undo log版本鏈 + ReadView機(jī)制,對于RC、RR如何基于這套機(jī)制實現(xiàn)多版本并發(fā)控制,就非常好理解了。


        首先,有一點非常重要,RC隔離級別下,一個事務(wù)每次發(fā)起查詢,都會生成一個ReadView。


        假設(shè)庫里有一行數(shù)據(jù),trx_id=50,現(xiàn)在有兩個事務(wù)A(id=60),事務(wù)B(id=70)并發(fā)執(zhí)行。


        事務(wù)B修改數(shù)據(jù)值為B,此時trx_id=70,如圖:

        這時候,事務(wù)B還沒提交,事務(wù)A發(fā)起查詢,那么就會生成已給ReadView。

        ReadView的m_ids里活躍的事務(wù)由60和70,此時事務(wù)A是無法查出事務(wù)B修改的值B的。于是順著版本鏈向下找,就找到trx_id=50的數(shù)據(jù)了。


        接著,事務(wù)B提交了,事務(wù)A再次發(fā)起查詢,又生成了一個ReadView。

        事務(wù)A再次基于ReadView查詢,發(fā)現(xiàn)這條數(shù)據(jù)的trx_id雖然在min_trx_id和max_trx_id之間,卻不在m_id里,說明事務(wù)B在生成本次ReadView之前已經(jīng)提交了,那么本次就可以查詢到事務(wù)B修改的這個值了。


        RC隔離級別如何實現(xiàn)的,級別就講完了,其關(guān)鍵在于每次查詢都會生成一個新的ReadView。


        可重復(fù)讀(RR)如何基于MVCC實現(xiàn)多事務(wù)并發(fā)控制?


        可重復(fù)讀隔離級別下,解決了臟讀、不可重復(fù)讀、幻讀這些問題,它是如何實現(xiàn)的呢?


        假設(shè),數(shù)據(jù)庫有一條數(shù)據(jù)trx_id=50,現(xiàn)在有兩個事務(wù)A(id=60),事務(wù)B(id= 70)并發(fā)執(zhí)行。


        事務(wù)A發(fā)起一個查詢,會生成一個ReadView。

        這個事務(wù)A基于這個ReadView去查這條數(shù)據(jù),會發(fā)現(xiàn)trx_id =50,小于ReadView里的min_trx_id,可以直接查出來。


        接著事務(wù)B修改數(shù)據(jù)值為B,此時會修改trx_id=70,然后提交事務(wù)。

        接著事務(wù)A第二次去查詢這條數(shù)據(jù),要知道它的ReadView沒有變。它會發(fā)現(xiàn)此時數(shù)據(jù)的trx_id=70在min_trx_id和max_trx_id之間,并且在m_ids中。那肯定不能查詢出來。于是順著undo log版本鏈向下找。


        找到了trx_id=50的數(shù)據(jù),這條數(shù)據(jù)是事務(wù)A開啟查詢之前提交了,可以返回。


        所有,RR隔離級別下,事務(wù)多次查詢,它的ReadView是不變的,這與RC是不同的,RC隔離級別下,每次查詢都會生成應(yīng)給ReadView。


        RR隔離級別下,就這樣解決了不可重復(fù)讀問題。


        由于RR隔離級別下,ReadView只會生成一次,那么你可以簡單的理解成,MySQL多事務(wù)并發(fā)執(zhí)行時,只能查詢到事務(wù)id比小于等于自己的數(shù)據(jù)。


        其實幻讀的解決方法與解決不可重復(fù)讀原理是一樣的,筆者這里就不再多贅述,有興趣的同學(xué)可以自己整理下思路,在腦子里過一下它內(nèi)部的運行流程。


        有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

        歡迎大家關(guān)注Java之道公眾號


        好文章,我在看??

        瀏覽 34
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            91精品国产色综合久久不卞臂 | 美女的骚逼 | 啊~揉一下就出水了 | 国产精品国产三级国产aⅴ浪潮 | 性xxxxfree盗摄国产高中 | www.91爱爱.com | 自拍偷拍视频网址 | 天天摸天天摸 | 性交网站在线 | 成人视频在线观看18 |