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

          MySQL是如何實現(xiàn)事物隔離?

          共 5465字,需瀏覽 11分鐘

           ·

          2021-06-07 08:17

          點擊上方藍色字體,選擇“標星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

            作者 |  JJian

          來源 |  urlify.cn/EryAbi

          前言

            眾所周知,MySQL的在RR隔離級別下查詢數(shù)據(jù),是可以保證數(shù)據(jù)不受其它事物影響,而在RC隔離級別下只要其它事物commit后,數(shù)據(jù)都會讀到commit之后的數(shù)據(jù),那么事物隔離的原理是什么?是通過什么實現(xiàn)的呢?那肯定是通過MVCC機制(Multi-Version Concurrency Control,即多版本并發(fā)控制),這是很多人知道的,但是我之前沒有好好分析過其實現(xiàn)原理,所以寫下此篇博文記錄下!

            注:MySQL的InnoDB引擎之所以能夠支持高性能的并發(fā)性能,就是由于MySQL的MVCC機制(歸功于undo log、Read-View、),但是本篇不對MVCC過多的介紹。

            參考資料:《MySQL實戰(zhàn)45講》系列,雖然講解的比較清晰,但是仍然需要理解,比如關(guān)于視圖數(shù)組部分我認為是相比較而言沒有解釋清楚,所以結(jié)合資料與自己見解加以記錄!

           


           

          一、RC與RR隔離級別

          我們分別開啟RC與RR隔離級別實驗說明,首先假設(shè)有account賬戶表,在事務(wù)ABC開啟前,賬戶中的余額balance為1,即

          select balance from account =1; # 結(jié)果為1


          1.RR事務(wù)隔離級別下查詢結(jié)果

          當(dāng)在RR事務(wù)隔離級別分別開啟三個事務(wù),在不同時間段內(nèi)做如下操作

          • 事務(wù)A(顯式開啟事務(wù),手動commit提交):查詢余額

          • 事務(wù)B(顯式開啟事務(wù),手動commit提交):對id=1的余額加1

          • 事務(wù)C(不顯式開啟事務(wù),自動提交):對id=1的余額加1

           我們從時間邏輯上分為三個階段,分析結(jié)果

          • 第一階段:事務(wù)A立馬開始事務(wù),隨后事務(wù)B也緊跟著立馬開始事務(wù),然后事務(wù)C首先更新balance為2成功,當(dāng)前balance=2;

          • 第二階段:事務(wù)B更新balance的值,此時先讀到當(dāng)前balance最新值為2,隨后set balance=balance+1成功,當(dāng)前balance=3;

          • 第三階段:事務(wù)A查詢balance的值,此時的值為1(這里為什么等于1呢,是怎么實現(xiàn)的呢?不應(yīng)該是當(dāng)前最新值3嗎?這就是本篇博文討論的重點,最后commit結(jié)束事務(wù),緊接著事務(wù)B也commit結(jié)束事務(wù)

           最后事務(wù)A讀取balance的結(jié)果是1,理所當(dāng)然,RR即為可重復(fù)讀,即一個事務(wù)在執(zhí)行過程中看到的數(shù)據(jù),總是跟這個事務(wù)啟動時看到的數(shù)據(jù)是一致的,當(dāng)前事務(wù)不管有沒有提交,都不會影響數(shù)據(jù),我只需要讀取基于快照的數(shù)據(jù)即可,這就是快照讀。但是我們要討論的是如何在MVCC機制下實現(xiàn)?

          注:begin/start transaction 命令并不是一個事務(wù)的起點,在執(zhí)行到它們之后的第一個操作InnoDB表的語句,事務(wù)才真正啟動。如果你想要馬上啟動一個事務(wù),可以使用start transaction with consistent snapshot 這個命令。

          1.RC事務(wù)隔離級別下查詢結(jié)果

          同樣地,我們在RC隔離下,開啟事務(wù)ABC,觀察事務(wù)A最后的balance結(jié)果。

           最后事務(wù)A讀取balance的結(jié)果是2,理所當(dāng)然,RC即為讀可提交,字面意思就是其他事務(wù)只要提交后,當(dāng)前事務(wù)我就能立馬讀取到最新當(dāng)前值,這就是當(dāng)前讀。但是我們要討論的是如何在MVCC機制下實現(xiàn)?

           實際上這是因為實現(xiàn)MVCC時用到的一致性讀視圖,即consistent read view,用于支持RC(Read Committed,讀提交)和RR(Repeatable Read,可重復(fù)讀)隔離級別的實現(xiàn)。

          三、事務(wù)隔離在MVCC的實現(xiàn)

          在探討MVCC如何實現(xiàn)事務(wù)隔離前,我們需要知道是視圖數(shù)組、一致性視圖等概念,才能幫助更好理解MVCC幫助事務(wù)實現(xiàn)了隔離。

          1.數(shù)據(jù)行ROW的多版本

            InnoDB里面每個事務(wù)有一個唯一的事務(wù)ID,叫作transaction id。它是在事務(wù)開始的時候向InnoDB的事務(wù)系統(tǒng)申請的,是按申請順序嚴格遞增的

            而每行數(shù)據(jù)也都是有多個版本的。每次事務(wù)更新數(shù)據(jù)的時候,都會生成一個新的數(shù)據(jù)版本,并且把transaction id賦值給這個數(shù)據(jù)版本的事務(wù)ID,記為row trx_id。同時,舊的數(shù)據(jù)版本要保留,并且在新的數(shù)據(jù)版本中,能夠有信息可以直接拿到它(通過undo_log文件找到)。

            也就是說,數(shù)據(jù)表中的一行記錄,其實可能有多個版本(row),每個版本有自己的row trx_id

            對某一個數(shù)據(jù)行ROW某個時刻經(jīng)過三次更新事務(wù)的多版本控制流程,畫如下圖加深理解。

          從圖我們可以得到:

          • ROW有四個版本V1-V4,即經(jīng)過三次更新balance后,當(dāng)前最新版本為V4,當(dāng)前balance已經(jīng)更新為4,是最新值

          • InnoDB每次更新事務(wù)產(chǎn)生的transaction id都會賦值給row trx_id;

          • 通過undo_log可以從V4撤回到V1,找到V1版本的balance=1,即undo_log回滾版本。

          明白了數(shù)據(jù)行的ROW的多版本原理與實現(xiàn)后,可以幫助我們理解InnoDB是怎么定義并創(chuàng)建快照的!

          2.視圖數(shù)組

            下述部分出自資料中的原句,特別是紅色加深部分可能會比較難以理解,所以需要結(jié)合自己理解并畫圖

          InnoDB是這么在事務(wù)開啟的時候定義快照的,哪些事務(wù)的操作我可以忽視,哪么我必須要保存在快照里。可以理解為:一個事務(wù)只需要在啟動的時候聲明說,“以我啟動的時刻為準,如果一個數(shù)據(jù)版本是在我啟動之前生成的,就認;如果是我啟動以后才生成的,我就不認,我必須要找到它的上一個版本”。

            在實現(xiàn)上, InnoDB為每個事務(wù)構(gòu)造了一個數(shù)組,用來保存這個事務(wù)啟動瞬間,當(dāng)前正在“活躍”的所有事務(wù)ID。“活躍”指的就是,啟動了但還沒提交。數(shù)組里面事務(wù)ID的最小值記為低水位,當(dāng)前系統(tǒng)里面已經(jīng)創(chuàng)建過的事務(wù)ID的最大值加1記為高水位。這個視圖數(shù)組和高水位,就組成了當(dāng)前事務(wù)的一致性視圖(read-view)。

          我對低水位與高水位的理解:

          低水位=當(dāng)前所有啟動了但未提交事務(wù)集合的ID最小值=當(dāng)前事務(wù)的上一個啟動但未提交的事務(wù)ID最小值(所有活躍事務(wù)ID最小值)

          高水位=當(dāng)前事務(wù)的ID(當(dāng)前ROW版本號/row trx_id)=已經(jīng)創(chuàng)建過事務(wù)ID的最大值+1

          舉例說明:仍然以上述RR隔離級別下三個ABC事務(wù)為例

          • 事務(wù)A開始前,系統(tǒng)里面只有一個活躍事務(wù)ID是99;

          • 事務(wù)A、B、C的版本號分別是100、101、102,且當(dāng)前系統(tǒng)里只有這四個事務(wù);

          • 三個事務(wù)開始前,(id,balance)=(1,1)這一行數(shù)據(jù)的row trx_id是90。

          這樣,事務(wù)A的視圖數(shù)組就是[99], 事務(wù)B的視圖數(shù)組是[99,100], 事務(wù)C的視圖數(shù)組是[99,100,101]即視圖數(shù)組通用公式為:[{當(dāng)前事務(wù)開啟瞬間活躍事務(wù)ID集合}]

           數(shù)據(jù)版本的可見性規(guī)則,就是基于row trx_id和一致性視圖對比結(jié)果得到的,所以我們還必須再了解下一致性視圖

          3.一致性視圖

          通過對視圖數(shù)組的理解,一致性視圖就更加容易了,即:這個視圖數(shù)組和高水位,就組成了當(dāng)前事務(wù)的一致性視圖(read-view)。

          仍然以上述RR隔離級別下三個ABC事務(wù)為例

          • 事務(wù)A開始前,系統(tǒng)里面只有一個活躍事務(wù)ID是99, 所以事物A開啟瞬間活躍事物集合為[99];

          • 事務(wù)A、B、C的版本號分別是100、101、102,且當(dāng)前系統(tǒng)里只有這四個事務(wù),所以事物A、B、C高水位分別為100、101、102

          • 三個事務(wù)開始前,(id,balance)=(1,1)這一行數(shù)據(jù)的row trx_id是90。

           這樣,事務(wù)A的一致性視圖就是[99,100], 事務(wù)B的一致性視圖是[99,100,101], 事務(wù)C的一致性視圖是[99,100,101,102]。即一致性視圖通用公式為:[{當(dāng)前事務(wù)開啟瞬間活躍事務(wù)ID集合},當(dāng)前row trx_id]

          分析上述流程圖結(jié)果:

          第一個有效更新版本是事物C,更新balance=2,這個時候的最新版本row trx_id=102,而之前的在事物ABC之前的活躍事物最新版本row trx_id為99,所以此時99已經(jīng)成為歷史版本1;

          第二個有效更新版本是事物B,更新balance=3,這個時候最新版本row trx_id=101,而此時row trx_id=102成為歷史版本1,而row trx_id=99成為歷史版本2;

          事物A查詢的時候,事物B是沒有提交,但生成的(id, balance)=(1, 3)已經(jīng)成為當(dāng)前最新版本,事物A讀取數(shù)據(jù)時,一致性視圖為[99, 100],而讀數(shù)據(jù)都是從當(dāng)前版本切的然后對比row trx_id,所以會有以下流程:

          • 找到(1,3)的時候,判斷出row trx_id=101,比高水位大,處于紅色區(qū)域,不可見;

          • 接著,找到上一個歷史版本,一看row trx_id=102,比高水位大,處于紅色區(qū)域,不可見;

          • 再往前找,終于找到了(1,1),它的row trx_id=90,比低水位小,處于綠色區(qū)域,可見

          最后事物A無論在什么時候查詢,看到的數(shù)據(jù)都是一致性視圖[99, 100]生成的快照數(shù)據(jù)(1, 1),即row trx_id=90時的數(shù)據(jù)。這就稱之為一致性讀

          總結(jié):

          對于一個事務(wù)視圖來說,除了自己的更新總是可見以外,有三種情況:

          • 版本未提交,不可見;

          • 版本已提交,但是是在視圖創(chuàng)建后提交的,不可見;

          • 版本已提交,而且是在視圖創(chuàng)建前提交的,可見。

          現(xiàn)在,我們用這個規(guī)則來判斷圖中的查詢結(jié)果,事務(wù)A的查詢語句的視圖數(shù)組是在事務(wù)A啟動的時候生成的,這時候:

          • (1,3)還沒提交,屬于情況1,不可見;

          • (1,2)雖然提交了,但是是在視圖數(shù)組創(chuàng)建之后提交的,屬于情況2,不可見;

          • (1,1)是在視圖數(shù)組創(chuàng)建之前提交的,可見

          4.當(dāng)前讀與快照讀

          (1)當(dāng)前讀與快照讀規(guī)則

            當(dāng)然按照這個一致性讀的邏輯,事物B在事物C有效更新balance=2之后,但是事物B的視圖數(shù)組是在事物C生成的,所以理論上來說不應(yīng)該是事物B看到的是(id, balance)=(1, 1)這個數(shù)據(jù)(快照/歷史版本)嗎?而看不到當(dāng)前版本(1, 2)數(shù)據(jù)。為什么事物B在更新balance之后直接數(shù)據(jù)就成為(1, 3)了呢?

            如果事物B在update之前select一次數(shù)據(jù),看到的值確實是balance=1,但是update是不能在歷史版本上操作的,否則事物C的更新就會丟失,所以update操作都是在先讀取當(dāng)前版本,然后再更新。

            也就說有這么一條規(guī)則:更新數(shù)據(jù)都是先讀后更新,而這個讀是讀當(dāng)前最新值,稱之為“當(dāng)前讀(current read),而只查詢不讀的話就會讀取當(dāng)前快照,稱之為“快照讀”所以在事物B更新balance之前,先查詢到最新的版本(1, 2)然后再更新為(1, 3)。而事物A查詢的快照數(shù)據(jù)為(1, 1),而不是最新版本(1, 3)。

          (2)當(dāng)前讀與快照讀解釋

            當(dāng)前讀:像select lock in share mode(共享鎖), select for update ; update, insert ,delete(排他鎖)這些操作都是一種當(dāng)前讀。就是它讀取的是記錄的最新版本,讀取時還要保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄,會對讀取的記錄進行加鎖。

            快照讀:像不加鎖的select操作就是快照讀,即不加鎖的非阻塞讀;快照讀的前提是隔離級別不是串行級別,串行級別下的快照讀會退化成當(dāng)前讀。是基于多版本控制的,那么快照讀可能讀到的并不一定是數(shù)據(jù)的最新版本,而有可能是之前的歷史版本(快照數(shù)據(jù))

          (3)RC讀可提交下的視圖規(guī)則

          讀提交的邏輯和可重復(fù)讀的邏輯類似,它們最主要的區(qū)別是:

          • 在可重復(fù)讀隔離級別下,只需要在事務(wù)開始的時候創(chuàng)建一致性視圖,之后事務(wù)里的其他查詢,都共用這個一致性視圖

          • 在讀提交隔離級別下,每一個語句執(zhí)行前都會重新算出一個新的視圖,此時start transaction with consistent snapshot就等同于普通的start transaction/begin

          所以在RC隔離級別下,事物A與事物B查詢到的數(shù)據(jù)分別如下:

          • 事物C立馬更新balance=2,然后自動提交,生成最新版本(1, 2),此時重新計算出視圖數(shù)據(jù)(1, 2);

          • 事物B查到此時的最新版本為(1, 2),之后再更新為版本(1, 3)為當(dāng)前最新版本,查詢此時的事物B select到的balance=3(事物B更新balance=3之后立馬算出一個新的視圖,select就是根據(jù)此視圖得到的數(shù)據(jù)),而不是1。

          • 而此時事物B還未提交,對于事物A來說是看不見的,所以事物A此時讀取到的事物C提交的最新版本(1, 2)








          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  中文字幕久久青青 | 豆花视频成人网站入口免费观看 | 亚洲乱伦第一页 | 色呦呦在线视频 | 精品一区一区三区四区 |