<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,看一遍就懂

          共 3054字,需瀏覽 7分鐘

           ·

          2021-10-24 08:14

          前文我們介紹了 InnoDB 存儲引擎在事務隔離級別 READ COMMITTED 和 REPEATABLE READ(默認)下會開啟一致性非鎖定讀,簡單回顧下:所謂一致性非鎖定讀就是每行記錄可能存在多個歷史版本,多版本之間串聯(lián)起來形成了一條版本鏈,這樣不同時刻啟動的事務可以無鎖地訪問到不同版本的數(shù)據(jù)。

          undo log 版本鏈

          一致性非鎖定讀是通過 MVCC(Multi Version Concurrency Control,多版本并發(fā)控制) 來實現(xiàn)的。事實上,MVCC 沒有一個統(tǒng)一的實現(xiàn)標準,所以各個存儲引擎的實現(xiàn)機制不盡相同。

          InnoDB 存儲引擎中 MVCC 的實現(xiàn)是通過 undo log 來完成的,undo log 是啥?

          簡單理解,undo log 就是每次操作的反向操作,比如比如當前事務執(zhí)行了一個插入 id = 100 的記錄的操作,那么 undo log 中存儲的就是刪除 id = 100 的記錄的操作。

          所以,這里用多版本來形容并不是非常準確,因為 InnoDB 并不會真正地去開辟空間存儲多個版本的行記錄,只是借助 undo log 記錄每次寫操作的反向操作。

          也就是說,B+ 索引樹上對應的記錄只會有一個最新版本,只不過 InnoDB 可以根據(jù) undo log 得到數(shù)據(jù)的歷史版本,從而實現(xiàn)多版本控制。

          那么,還有個問題,undo log 是如何和某條行記錄產(chǎn)生聯(lián)系的呢?換句話說,我怎么能通過這條行記錄找到它擁有的 undo log 呢?

          具體來說,InnoDB 存儲引擎中每條行記錄其實都擁有兩個隱藏的字段:trx_idroll_pointer

          從名字也能看出來,trx_id 就是最近更新這條行記錄的事務 ID,roll_pointer 就是指向之前生成的 undo log。

          掏出我們的 user 表,來舉個例子,假設(shè) id = 100 的事務 A 插入一條行記錄(id = 1, username = "Jack", age = 18),那么,這行記錄的兩個隱藏字段 trx_id = 100 ?和 roll_pointer 指向一個空的 undo log,因為在這之前并沒有事務操作 id = 1 的這行記錄。如圖所示:

          然后,id = 200 的事務 B 修改了這條行記錄,把 age 從 18 修改成了 20,于是,這條行記錄的 trx_id 就變成了 200,rooll_pointer 就指向事務 A 生成的 undo log :

          接著,id = 300 的事務 C 再次修改了這條行記錄,把 age 從 20 修改成了 30,如下圖:

          可以看到,每次修改行記錄都會更新 trx_id 和 roll_pointer 這兩個隱藏字段,之前的多個數(shù)據(jù)快照對應的 undo log 會通過 roll_pointer 指針串聯(lián)起來,從而形成一個版本鏈

          需要注意的是,select 查詢操作不會生成 undo log!在 InnoDB 存儲引擎中,undo log 只分為兩種:

          • insert undo log:在 insert 操作中產(chǎn)生的 undo log
          • update undo log:對 delete 和 update 操作產(chǎn)生的 undo log

          事實上,由于事務隔離性的要求,insert 操作的記錄,只對事務本身可見,對其他事務不可見,也即插入操作不會對已經(jīng)存在的記錄產(chǎn)生影響!,所以也就不存在并發(fā)情況下的問題。所以,也就是說,MVCC 這個機制,其實就是靠 update undo log 實現(xiàn)的,和 insert undo log 基本上沒啥關(guān)系,我們上面說的 undo log 版本鏈上的其實就是 update undo log。

          ReadView 機制

          說到 MVCC,說到 undo log 版本鏈,如果你自己不往下說的話,八九不離十面試官都會問你下 ReadView 這個機制。

          咱也不賣官子,直接說吧,ReadView 機制就是用來判斷當前事務能夠看見哪些版本的,一個 ReadView 主要包含如下幾個部分:

          • m_ids:生成 ReadView 時有哪些事務在執(zhí)行但是還沒提交的(稱為 ”活躍事務“),這些活躍事務的 id 就存在這個字段里
          • min_trx_id:m_ids 里最小的值
          • max_trx_id:生成 ReadView 時 InnoDB 將分配給下一個事務的 ID 的值(事務 ID 是遞增分配的,越后面申請的事務 ID 越大)
          • creator_trx_id:當前創(chuàng)建 ReadView 事務的 ID

          接下來,再掏出 user 表,通過一個例子來理解下 ReaView 機制是如何做到判斷當前事務能夠看見哪些版本的:

          假設(shè)表中已經(jīng)被之前的事務 A(id = 100)插入了一條行記錄(id = 1, username = "Jack", age = 18),如圖所示:

          接下來,有兩個事務 B(id = 200) 和 C(id = 300)過來并發(fā)執(zhí)行,事務 B 想要更新(update)這行 id = 1 的記錄,而事務 C(select)想要查詢這行數(shù)據(jù),這兩個事務都執(zhí)行了相應的操作但是還沒有進行提交:

          如果現(xiàn)在事務 B 開啟了一個 ReadView,在這個 ReadView 里面:

          • m_ids 就包含了當前的活躍事務的 id,即事務 B 和事務 C 這兩個 id,200 和 300
          • min_trx_id 就是 200
          • max_trx_id 是下一個能夠分配的事務的 id,那就是 301
          • creator_trx_id 是當前創(chuàng)建 ReadView 事務 B 的 id 200

          現(xiàn)在事務 B 進行第一次查詢(上面說過 select 操作不會生成 undo log 的哈),會把這行記錄的隱藏字段 trx_id 和 ReadView 的 min_trx_id 進行下判斷,此時,發(fā)現(xiàn) trx_id 是 100,小于 ReadView 里的 min_trx_id(200),這說明在事務 B 開始之前,修改這行記錄的事務 A 已經(jīng)提交了,所以開始于事務 A 提交之后的事務 B、是可以查到事務 A 對這行記錄的更新的

          row.trx_id?

          接著事務 C 過來修改這行記錄,把 age = 18 改成了 age = 20,所以這行記錄的 trx_id 就變成了 300,同時 roll_pointer 指向了事務 C 修改之前生成的 undo log:

          那這個時候事務 B 再次進行查詢操作,會發(fā)現(xiàn)這行記錄的 trx_id(300)大于 ReadView 的 min_trx_id(200),并且小于 max_trx_id(301)

          row.trx_id?>?ReadView.min_trx_id?&&?row.trx_id?

          這說明一個問題,就是更新這行記錄的事務很有可能也存在于 ReadView 的 m_ids(活躍事務)中。所以事務 B 會去判斷下 ReadView 的 m_ids 里面是否存在 trx_id = 300 的事務,顯然是存在的,這就表示這個 id = 300 的事務是跟自己(事務 B)在同一時間段并發(fā)執(zhí)行的事務,也就說明這行 age = 20 的記錄事務 B 是不能查詢到的。

          既然無法查詢,那該咋整?事務 B 這次的查詢操作能夠查到啥呢?

          沒錯,undo log 版本鏈!

          這時事務 B 就會順著這行記錄的 roll_pointer 指針往下找,就會找到最近的一條 trx_id = 100 的 undo log,而自己的 id 是 200,即說明這個 trx_id = 100 的 undo log 版本必然是在事務 B 開啟之前就已經(jīng)提交的了。所以事務 B 的這次查詢操作讀到的就是這個版本的數(shù)據(jù)即 age = 18。

          通過上述的例子,我們得出的結(jié)論是,通過 undo log 版本鏈和 ReadView 機制,可以保證一個事務不會讀到并發(fā)執(zhí)行的另一個事務的更新


          那自己修改的值,自己能不能讀到呢?

          這當然是廢話,肯定可以讀到呀。不過上面的例子我們只涉及到了 ReadView 中的前三個字段,而 creator_trx_id 就與自己讀自己的修改有關(guān),所以這里還是圖解出來讓大家更進一步理解下 ReadView 機制:

          假設(shè)事務 C 的修改已經(jīng)提交了,然后事務 B 更新了這行記錄,把 age = 20 改成了 age = 66,如下圖所示:

          然后,事務 B 再來查詢這條記錄,發(fā)現(xiàn) trx_id = 200 與 ReadView 里的 creator_trx_id = 200 一樣,這就說明這是我自己剛剛修改的啊,當然可以被查詢到。

          row.trx_id?=?ReadView.creator_trx_id

          那如果在事務 B 的執(zhí)行期間,突然開了一個 id = 400 的事務 D,然后更新了這行記錄的 age = 88 并且還提交了,然后事務 B 再去讀這行記錄,能讀到嗎?

          答案是不能的。

          因為這個時候事務 B 再去查詢這行記錄,就會發(fā)現(xiàn) trx_id = 500 大于 ReadView 中的 max_trx_id = 301,這說明事務 B 執(zhí)行期間,有另外一個事務更新了數(shù)據(jù),所以不能查詢到另外一個事務的更新。

          row.trx_id?>?ReadView.max_trx_id

          那通過上述的例子,我們得出的結(jié)論是,通過 undo log 版本鏈和 ReadView 機制,可以保證一個事務只可以讀到該事務自己修改的數(shù)據(jù)或該事務開始之前的數(shù)據(jù)

          小結(jié)

          總結(jié)下,通過 undo log 版本鏈和 ReadView 機制:

          • 可以保證一個事務不會讀到并發(fā)執(zhí)行的另一個事務的更新
          • 可以保證一個事務只可以讀到該事務自己修改的數(shù)據(jù)或該事務開始之前的數(shù)據(jù)

          另外,前文說過,一致性非鎖定讀(或者直接說 MVCC 吧,畢竟一致性非鎖定讀也是靠 MVCC 實現(xiàn)的)只在事務隔離級別 READ COMMITTED 和 REPEATABLE READ(默認)下才會開啟,那對于這兩個隔離級別,其實最根本的不同之處,就在于它們生成 ReadView 的時機不同,這個我們留在下文解釋~


          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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片免费看 | 男人在线天堂 | 91精品久久久久久久不卡 | 激情乱伦网站 |