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

          看看大神是如何 圖解 MVCC 的?

          共 3268字,需瀏覽 7分鐘

           ·

          2022-05-10 03:03

          前言

          大家好,我是fancy呀。

          在上一篇關(guān)于MySQL的文章中,我講到了事務(wù)的特性、隔離級(jí)別和并發(fā)一致性問題。其中我們說到了數(shù)據(jù)庫(kù)的四個(gè)隔離級(jí)別,并說明MVCC是實(shí)現(xiàn)了提交讀,可重復(fù)讀的重要手段。

          MVCC也是MySQL數(shù)據(jù)庫(kù)中一個(gè)老生常談的話題了,但是由于它較為底層,實(shí)際的開發(fā)日常中我們并不會(huì)去直接接觸它,所以真正將它弄明白的人并不多,許多面試者,提到它很多人都處于:“哦,這個(gè)東西我知道!是數(shù)據(jù)庫(kù)中的一種并發(fā)措施,但是我有點(diǎn)忘記了它的詳細(xì)內(nèi)容了...”這樣的狀態(tài)。

          所以本篇文章,就來詳細(xì)講一講”MVCC”,帶大家理清楚MVCC的內(nèi)容。

          話不多說,列大綱,發(fā)車!

          什么是MVCC?

          MVCC((Mutil-Version Concurrency Control)),全稱多版本并發(fā)訪問,這是一種并發(fā)環(huán)境下進(jìn)行數(shù)據(jù)安全控制的方法,其本質(zhì)上是一種樂觀鎖,用于實(shí)現(xiàn)提交讀(READ COMMITTD)和可重復(fù)讀(REPEATABLE READ)這兩種隔離級(jí)別。在這里我先為大家理清楚一個(gè)概念:我們常說的MVCC是由MySQL數(shù)據(jù)庫(kù)InnoDB存儲(chǔ)引擎實(shí)現(xiàn)的,并非是由MySQL本身實(shí)現(xiàn)的,不同的存儲(chǔ)引擎,對(duì)MVCC都有不同的實(shí)現(xiàn)標(biāo)準(zhǔn)。所以當(dāng)你在面試官面前說:”MySQL使用了MVCC實(shí)現(xiàn)了xxxxxx“,面試官一下子就把你給逮住了,說明你連MySQL和InnoDB之間的關(guān)系都模糊不清??。


          那么,當(dāng)你聽到了“多版本”、“并發(fā)訪問”這兩個(gè)詞,你也許就知道了,InnoDB使用MVCC來解決并發(fā)問題的方法,就是讓每個(gè)不同的事務(wù)訪問查詢同一行數(shù)據(jù)時(shí),每個(gè)事務(wù)修改的都是這行數(shù)據(jù)的不同版本,InnoDB只需要去記錄這個(gè)數(shù)據(jù)的訪問鏈,就可以實(shí)現(xiàn)一個(gè)SELECT操作的并發(fā)執(zhí)行。

          那么,它具體是如何實(shí)現(xiàn)的呢?

          MVCC利用了多版本的思想,在 MVCC 中事務(wù)的所有寫操作(INSERT、UPDATE、DELETE)會(huì)為數(shù)據(jù)行新增一個(gè)最新的版本快照,而讀操作是去讀舊版本的快照,也就是說,讀操作和寫操作是分離的,二者之間沒有依賴、互斥關(guān)系。

          核心

          Undo Log

          什么是Undo Log?

          Undo Log是MySQL的三大日志之一,當(dāng)我們對(duì)記錄做了變更操作時(shí)就會(huì)產(chǎn)生一條Undo記錄。它的作用就是保護(hù)事務(wù)在異常發(fā)生的時(shí)候或手動(dòng)回滾時(shí)可以回滾到歷史版本數(shù)據(jù),能夠讓你讀取過去某一個(gè)時(shí)間點(diǎn)保存的數(shù)據(jù)。通俗易懂地說,它只關(guān)心過去的數(shù)據(jù)。

          本文我們不會(huì)對(duì)Undo Log的作用做太多描述,你需要重點(diǎn)知道的是:對(duì)于一個(gè)InnoDB存儲(chǔ)引擎,一個(gè)聚簇索引(主鍵索引)的記錄之中,一定會(huì)有兩個(gè)隱藏字段trx_idroll_pointer,這兩個(gè)字段存儲(chǔ)于B+樹的葉子節(jié)點(diǎn)中,分別對(duì)應(yīng)記錄著兩列信息:

          trx_id:只要有任意一個(gè)事務(wù)對(duì)某條聚簇索引記錄進(jìn)行修改,該事務(wù)id就會(huì)被記錄到該字段里面。

          roll_pointer:當(dāng)任意一個(gè)聚簇索引記錄被修改,上一個(gè)版本的數(shù)據(jù)記錄就會(huì)被寫入U(xiǎn)ndo Log日志里面。那么這個(gè)roll_pointer就是存儲(chǔ)了一個(gè)指針,這個(gè)指針是一個(gè)地址,指向這個(gè)聚簇索引的上一個(gè)版本的記錄位置,通過這個(gè)指針就可以獲得到每一個(gè)歷史版本的記錄。

          OK,假設(shè)我們現(xiàn)在有一張員工表employee,有主鍵id、name、age三個(gè)字段。使用一個(gè)事務(wù)A創(chuàng)建第一條主鍵id為1的記錄,把名字修改為fancy,他的年齡為25歲。
          那么,這條記錄的trx_id隱藏字段就會(huì)記錄此次插入記錄的事務(wù)ID:


          假設(shè)現(xiàn)在有一個(gè)事務(wù)B,事務(wù)ID為20,要對(duì)這條記錄進(jìn)行修改,把fancy的age從25改成了28,那么此時(shí)Undo Log會(huì)發(fā)生啥?
          此時(shí),這這條id為1的記錄,trx_id就變?yōu)榱?0,是的。trx_id此時(shí)記錄了修改這條記錄的事務(wù)ID,而對(duì)應(yīng)的roll_pointer指針,就指向了上次事務(wù)A的操作對(duì)應(yīng)的Undo Log:


          好,所以這里先總結(jié)一下:trx_id就是記錄修改了每條聚簇索引的事務(wù)id;roll_pointer顧名思義就是個(gè)指針,指向每一個(gè)歷史操作版本的數(shù)據(jù)存儲(chǔ)的地址;每一次修改操作都會(huì)生成一個(gè)Undo Log版本,每個(gè)版本之間是隔離的。

          如果接下去有事務(wù)C,事務(wù)D等等一直對(duì)這條記錄進(jìn)行修改,那么這條記錄的roll_pointer指針就會(huì)一直這樣遞歸修改下去,最終形成一個(gè)關(guān)于修改和刪除操作的Undo Log版本鏈!

          為什么我說是關(guān)于修改和刪除操作的版本鏈,而沒有查詢操作的呢?因?yàn)椋?span style="box-sizing: border-box;font-weight: 600;">查詢操作不會(huì)生成Undo Log版本鏈。

          那么,InnoDB有幾種版本鏈呢?其實(shí)只有兩種:insert undo log(插入操作產(chǎn)生) 和update undo log(更新操作產(chǎn)生)。
          就像我開頭所說的,讀操作和寫操作是分離的,它們之間沒有關(guān)系。寫操作去生成版本鏈,而讀操作只需要根據(jù)規(guī)則去查看對(duì)應(yīng)的某一個(gè)版本,然后讀取就完事了。至于是什么規(guī)則?就是我們下文要說的:Read View。

          Read View

          什么是Read View?

          Read View 存放著一個(gè)列表,這個(gè)列表用來記錄當(dāng)前數(shù)據(jù)庫(kù)系統(tǒng)中活躍的讀寫事務(wù),也就是已經(jīng)開啟了,正在進(jìn)行數(shù)據(jù)操作但是還未提交保存的事務(wù)??梢酝ㄟ^這個(gè)列表來判斷某一個(gè)版本是否對(duì)當(dāng)前事務(wù)可見。其中,有四個(gè)重要的字段:

          creator_trx_id:創(chuàng)建當(dāng)前Read View所對(duì)應(yīng)的事務(wù)ID

          m_ids:所有當(dāng)前未提交事務(wù)的事務(wù)ID,也就是活躍事務(wù)的事務(wù)id列表

          min_trx_id:m_ids里最小的事務(wù)id值

          max_trx_id:InnoDB 需要分配給下一個(gè)事務(wù)的事務(wù)ID值(事務(wù) ID 是累計(jì)遞增分配的,所以后面分配的事務(wù)ID一定會(huì)比前面的大?。?br style="box-sizing: border-box;">

          MVCC里面最難的估計(jì)就是記住這四個(gè)字段的概念了。但是由于它們非常重要,如何判斷當(dāng)前版本對(duì)每一個(gè)事務(wù)是否可見,就是拿它們的值進(jìn)行區(qū)間比較,所以請(qǐng)你務(wù)必要記住它們?。接下來我也會(huì)通過詳細(xì)的圖解為大家說明這一過程。

          MVCC如何實(shí)現(xiàn)可重復(fù)讀?

          ok,假設(shè)現(xiàn)在事務(wù)A和事務(wù)B同時(shí)對(duì)主鍵id = 1進(jìn)行操作。事務(wù)A的id為20,事務(wù)B的id為30。


          那么這兩個(gè)事務(wù)就會(huì)創(chuàng)建各自的Read View:


          此時(shí)事務(wù)A的creator_trx_id=20,事務(wù)B的creator_trx_id=30。

          由于僅有兩個(gè)活躍的事務(wù),事務(wù)id分別為20和30。事務(wù)列表中最小的的事務(wù)id是事務(wù)A,所以min_trx_id應(yīng)該為20,下一個(gè)也就是最大的事務(wù)id的max_trx_id值應(yīng)該為事務(wù)B的下一個(gè)id,也就是31。

          事務(wù)A去讀取主鍵id為1的數(shù)據(jù),找到了記錄后就會(huì)去查看該記錄的trx_id,事務(wù)A查看到該記錄的trx_id值為10。


          隨后和自己的creator_trx_id值進(jìn)行比較:

          發(fā)現(xiàn)主鍵id=1這條記錄.trx_id = 10 < 自己.creator_trx_id = 20,就判斷到該記錄的事務(wù)id不存在于活躍的事務(wù)列表中并且小于自己的事務(wù)id,這代表本次記錄S的值是在自己查詢之前提交的,那就可以放心的讀取啦。讀取完之后,會(huì)將該記錄的trx_id修改為自己的事務(wù)id。


          然后,把fancy的age從28又改成了30??。


          那么還有什么字段此時(shí)也會(huì)被修改嗎?沒錯(cuò),就是我們剛剛提到的,Undo Log的另一個(gè)隱藏字段:roll_pointer指針。它會(huì)去指向被事務(wù)A修改之前的版本,也就是fancy的年齡還是28時(shí)候的地址,就為了用來記錄,方便下次被查詢:

          隨后,事務(wù)B也進(jìn)來了,哈哈。它也要進(jìn)行對(duì)主鍵id為1的update操作。把fancy的年齡直接從30更改為50!此時(shí)會(huì)再次進(jìn)行一次trx_id的比較過程,去判斷自己的creator_trx_id是否大于這條記錄對(duì)應(yīng)的trx_id,如果大于,就去修改這條記錄的值,將年齡從30修改為50:


          重點(diǎn)來了!

          過了一會(huì),如果事務(wù)A再次去讀取主鍵id=1這行的值,發(fā)現(xiàn)這條記錄的trx_id已經(jīng)變成了30,就會(huì)再次進(jìn)行值的區(qū)間比較:發(fā)現(xiàn)自己.trx_id(20)<主鍵id=1這條記錄.trx_id(30)和自己同一時(shí)間范圍內(nèi)一塊啟動(dòng)的另一個(gè)未提交的活躍事務(wù)所修改的值。


          那么此時(shí)事務(wù)A是不會(huì)去讀取這條記錄對(duì)應(yīng)的數(shù)據(jù)的,它會(huì)通過Undo Log上的roll_pointer指向的地址去查找上一個(gè)舊版本的記錄,直到找到第一條trx_id小于等于自己的事務(wù)id并且不存在于m_ids列表中的記錄,代表是別的事務(wù)已經(jīng)提交的最后一條記錄,然后讀取它。

          這樣子,每一個(gè)事務(wù)去讀取或者修改同一個(gè)記錄時(shí),只能操作已經(jīng)提交了的數(shù)據(jù),未提交的數(shù)據(jù)是不能讀取到的??芍貜?fù),就這樣實(shí)現(xiàn)了。

          其實(shí)就是通過Read View的字段判斷這行記錄對(duì)自己是否可見,如果不可見的話再去找Undo Log里面記錄的對(duì)自己可見的數(shù)據(jù),然后操作就完事了。

          怎么樣,是不是很簡(jiǎn)單?

          那么,我們?cè)賮砜纯碝VCC如何實(shí)現(xiàn)提交讀。

          MVCC如何實(shí)現(xiàn)提交讀?

          我們先來回顧一下什么是提交讀,提交讀能夠解決臟讀這個(gè)并發(fā)一致性問題。臟讀問題本質(zhì)上就是一個(gè)事務(wù)讀取到了另一個(gè)事務(wù)沒有提交的內(nèi)容。那么Read View要如何解決這個(gè)問題呢?道理其實(shí)一樣的。

          假設(shè)事務(wù)A和事務(wù)B依然同一時(shí)刻啟動(dòng),事務(wù)B將同一行的記錄,也就是fancy的年齡又改成了25,但是并沒有提交哦,所以此時(shí)事務(wù)A就會(huì)去讀取這條記錄的trx_id。


          事務(wù)A查看到該記錄的trx_id居然比事務(wù)A的Read View列表里面的creator_trx_id值大,并且修改這條記錄的事務(wù)的trx_id存在于自己的m_ids列表里面,那么事務(wù)A就可以判斷得到該記錄是被另一條沒有提交的事務(wù)修改的,所以它不會(huì)去讀取這條數(shù)據(jù)的內(nèi)容,事務(wù)A會(huì)繼續(xù)通過Undo Log往下找第一條trx_id小于等于自己的事務(wù)id并且不在活躍事務(wù)列表m_ids里面的數(shù)據(jù)。由此,便不會(huì)看到別的事務(wù)正在修改的數(shù)據(jù),臟數(shù)據(jù)也不會(huì)產(chǎn)生。

          總結(jié)

          通過以上描述,我們就可以清楚的知道:InnoDB 中,MVCC 就是通過 Undo Log + Read View 進(jìn)行數(shù)據(jù)讀取,Undo Log 保存了歷史快照,而?Read View 規(guī)則幫我們判斷當(dāng)前版本的數(shù)據(jù)是否可見。從而不需要通過加鎖的方式,就可以實(shí)現(xiàn)提交讀和可重復(fù)讀這兩種隔離級(jí)別。

          總的來說,MVCC本質(zhì)上就是一種數(shù)據(jù)結(jié)構(gòu)。已提交讀和可重復(fù)讀都是使用了Read View這種策略通過區(qū)間判斷獲取自己能夠讀取的內(nèi)容,然后展示。InnoDB通過MVCC,解決了臟讀、不可重復(fù)讀。但是InnoDB如何去解決幻讀呢?光靠MVCC其實(shí)是不夠的,InnoDB通過MVCC + Next-Key Lock(臨鍵鎖)來解決幻讀,那么下一篇文章,我們就來詳細(xì)聊聊InnoDB的各種相關(guān)的鎖,再說說InnoDB是如何解決幻讀的。


          瀏覽 27
          點(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>
                  一级黄色在线 | 男女拍拍软件视频 | AV电影亚洲| 91乱轮视频 | 国产成人精品午夜精品 |