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

          共 4685字,需瀏覽 10分鐘

           ·

          2021-09-25 18:59

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

          undo log 版本鏈

          一致性非鎖定讀是通過(guò) MVCC(Multi Version Concurrency Control,多版本并發(fā)控制) 來(lái)實(shí)現(xiàn)的。事實(shí)上,MVCC 沒(méi)有一個(gè)統(tǒng)一的實(shí)現(xiàn)標(biāo)準(zhǔn),所以各個(gè)存儲(chǔ)引擎的實(shí)現(xiàn)機(jī)制不盡相同。

          InnoDB 存儲(chǔ)引擎中 MVCC 的實(shí)現(xiàn)是通過(guò) undo log 來(lái)完成的,undo log 是啥?

          簡(jiǎn)單理解,undo log 就是每次操作的反向操作,比如比如當(dāng)前事務(wù)執(zhí)行了一個(gè)插入 id = 100 的記錄的操作,那么 undo log 中存儲(chǔ)的就是刪除 id = 100 的記錄的操作。

          所以,這里用多版本來(lái)形容并不是非常準(zhǔn)確,因?yàn)?InnoDB 并不會(huì)真正地去開(kāi)辟空間存儲(chǔ)多個(gè)版本的行記錄,只是借助 undo log 記錄每次寫(xiě)操作的反向操作。

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

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

          具體來(lái)說(shuō),InnoDB 存儲(chǔ)引擎中每條行記錄其實(shí)都擁有兩個(gè)隱藏的字段:trx_idroll_pointer。

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

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

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

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

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

          需要注意的是,select 查詢(xún)操作不會(huì)生成 undo log!在 InnoDB 存儲(chǔ)引擎中,undo log 只分為兩種:

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

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

          ReadView 機(jī)制

          說(shuō)到 MVCC,說(shuō)到 undo log 版本鏈,如果你自己不往下說(shuō)的話,八九不離十面試官都會(huì)問(wèn)你下 ReadView 這個(gè)機(jī)制。

          咱也不賣(mài)官子,直接說(shuō)吧,ReadView 機(jī)制就是用來(lái)判斷當(dāng)前事務(wù)能夠看見(jiàn)哪些版本的,一個(gè) ReadView 主要包含如下幾個(gè)部分:

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

          接下來(lái),再掏出 user 表,通過(guò)一個(gè)例子來(lái)理解下 ReaView 機(jī)制是如何做到判斷當(dāng)前事務(wù)能夠看見(jiàn)哪些版本的:

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

          接下來(lái),有兩個(gè)事務(wù) B(id = 200) 和 C(id = 300)過(guò)來(lái)并發(fā)執(zhí)行,事務(wù) B 想要更新(update)這行 id = 1 的記錄,而事務(wù) C(select)想要查詢(xún)這行數(shù)據(jù),這兩個(gè)事務(wù)都執(zhí)行了相應(yīng)的操作但是還沒(méi)有進(jìn)行提交:

          如果現(xiàn)在事務(wù) B 開(kāi)啟了一個(gè) ReadView,在這個(gè) ReadView 里面:

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

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

          row.trx_id < ReadView.min_trx_id

          接著事務(wù) C 過(guò)來(lái)修改這行記錄,把 age = 18 改成了 age = 20,所以這行記錄的 trx_id 就變成了 300,同時(shí) roll_pointer 指向了事務(wù) C 修改之前生成的 undo log:

          那這個(gè)時(shí)候事務(wù) B 再次進(jìn)行查詢(xún)操作,會(huì)發(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 < max_trx_id

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

          既然無(wú)法查詢(xún),那該咋整?事務(wù) B 這次的查詢(xún)操作能夠查到啥呢?

          沒(méi)錯(cuò),undo log 版本鏈!

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

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


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

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

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

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

          row.trx_id = ReadView.creator_trx_id

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

          答案是不能的。

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

          row.trx_id > ReadView.max_trx_id

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

          小結(jié)

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

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

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

          我是小牛肉,長(zhǎng)風(fēng)破浪會(huì)有時(shí),小伙伴們下篇文章再見(jiàn) ??


          • 博主小碩在讀,深耕 Java,目前在維護(hù)一個(gè)教程類(lèi)倉(cāng)庫(kù) CS-Wiki「Gitee 官方推薦項(xiàng)目,現(xiàn)已 1.9k+ star,倉(cāng)庫(kù)地址:https://gitee.com/veal98/CS-Wiki」,公眾號(hào)上的文章也會(huì)在此同步更新,歡迎各位前來(lái)交流學(xué)習(xí)

          • 準(zhǔn)備春招秋招的小伙伴可以參考我的這個(gè)論壇項(xiàng)目 Echo「Gitee 官方推薦項(xiàng)目,現(xiàn)已 1.1k+ star,倉(cāng)庫(kù)地址:https://gitee.com/veal98/Echo」。配套教程正在同步更新中,公眾號(hào)后臺(tái)回復(fù) "Echo" 即可免費(fèi)獲取。

          瀏覽 48
          點(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电影在线 | 鸡巴视频国产 | 少妇操屄视频 |