1. 程式人生 > 資料庫 >MySQL高階系列--MVCC

MySQL高階系列--MVCC

其他網址


簡介

        多版本併發控制(Multi-Version Concurrency Control, MVCC),顧名思義,在併發訪問的時候,資料存在版本的概念,可以有效地提升資料庫併發能力,常見的資料庫如MySQL、MS SQL Server、IBM DB2、Hbase、MongoDB等等都在使用。簡單講,如果沒有MVCC,當想要讀取的資料被其他事務用排它鎖鎖住時,只能互斥等待;而這時MVCC可以通過提供歷史版本從而實現讀取被鎖的資料(的歷史版本),避免了互斥等待。

在 MySQL中,MVCC是 InnoDB 儲存引擎實現隔離級別的一種具體方式。

  • 未提交讀:無需使用 MVCC(總是讀取最新的資料行)
  • 提交讀可重複讀:使用MVCC來實現。
  • 可序列化:需要對所有讀取的行都加鎖,單純使用 MVCC 無法實現。

MVCC一般有兩種實現方式,本文所講的InnoDB採用的是後者:

  • 實時保留資料的一個或多個歷史版本
  • 在需要時通過undo日誌構造出歷史版本

快照讀與當前讀

簡介

 

原理

        事務ID是在mysql開啟事務時為其分配的遞增序列號,由於是遞增的,所以可以基於此判斷事務先後關係。

        MVCC的多版本指的是針對資料庫中的一行資料,都可能通過undolog中的資料算出多條行資料,每行資料版本不同(是為多版本),針對每次寫操作,事務提交前,都會在undolog中記錄相應的變動(是為回滾log),以及對應的事務ID,再結合資料表中的當前行資料,就可以回溯出一個行的的多個版本了。

        Innodb會為每行資料新增兩個欄位 up_txid、del_txid,分別是更新事務ID、刪除事務ID,事務新增或者更新一個數據行後,會將該事務ID記錄在該行資料的up_txid中,事務刪除行資料後,會將該事務ID記錄在del_txid中。

在read repeatable隔離級別下

        該隔離級別下的事務啟動時,除了分配上面說的事務ID外,系統還會查出當前活躍的事務ID列表(也就是開啟了但還未提交的事務),分配給該事務儲存下來,有了這些資訊,就可以實現快照讀了,RR隔離級別下,其查詢到的行資料需要滿足:

  1. 行資料的up_txid<=當前事務ID,並且不在活躍事務ID列表中
  2. 行資料的del_txid為null,或者>當前事務ID,或者在活躍事務ID列表中

        簡單理解下,只查詢在當前事務開啟之前就已經提交的資料,並且這行資料未被刪除或者在當前事務開啟後刪除,相當於事務啟動時,拍了個快照,事務執行期間,就通過這個快照讀取資料,其他事務的變動不會再對當前事務產生影響,是為可重複讀

        在讀取時,會從最新的一條資料開始讀起,如果滿足條件就以其為準,如果不滿足就找到更舊的一行資料繼續判斷。

read committed隔離級別下

        和RR隔離級別一樣的是,RC隔離級別下的查詢也是快照讀,區別就是RC隔離級別下每次select時都會獲取下當前活躍事務ID列表,然後從最新一行資料開始,判斷是否滿足如下條件,不滿足則繼續判斷更舊的一行資料:

  1. 行資料的up_txid不在活躍事務ID列表中,表示已經提交
  2. 行資料的del_txid為null,或者在活躍事務ID列表中未提交

簡單理解下,就是每次都讀取當前已經提交的並且未被刪除的最新資料,相當於每次查詢都會拍個快照

當前讀

        如果查詢加了鎖,就不在mvcc的控制範疇了,因為此時用的是當前讀 。當前讀的規則,就是要能讀到所有已經提交的記錄的最新值。當前讀是由鎖來保證的。Innodb中有行鎖,上面舉例的幾條語句,都會鎖住id=1的這行資料,這樣其他事務如果要對id=1這行資料進行當前讀,只能等行鎖釋放,等到啥時候?事務完成的時候會釋放掉鎖,既然事務都完成了,那其他事務自然能讀取到已提交的最新值。

MVCC原理簡述

在Mysql中MVCC是在Innodb儲存引擎中得到支援的,InnoDb的最基本的行中包含一些額外的儲存資訊:DATA_TRX_ID,DATA_ROLL_PTR,DB_ROW_ID,DELETE BITInnodb為每行記錄都實現了三個隱藏欄位:

  • 6位元組的事務ID(DB_TRX_ID )。
    (該行所的事務id,每處理一個事務,其值自動+1。可以基於此判斷事務先後關係)
  • 7位元組的回滾指標(DB_ROLL_PTR)。
    (指向當前記錄項的rollback segment的undo log記錄,找之前版本的資料就是通過這個指標)
  • 6位元組的隱式主鍵(DB_ROW_ID)。
    Innodb自動產生聚集索引時,聚集索引包括這個DB_ROW_ID的值,否則聚集索引中不包括這個值,這個用於索引當中。
  • 刪除標識位(DELETE BIT)。
    用於標識該記錄是否被刪除,這裡的不是真正的刪除資料,而是標誌出來的刪除。真正意義的刪除是在commit的時候

MVCC併發控制的執行過程

以update為例:begin=> 用排他鎖鎖定該行=> 記錄redo log=> 記錄undo log=> 修改當前行的值,寫事務編號

  • SELECT
    Innodb檢查每行資料,確保他們符合兩個標準:
    1、InnoDB只查詢版本早於當前事務版本的資料行(也就是資料行的版本必須小於等於事務的版本),這確保當前事務讀取的行都是事務之前已經存在的,或者是由當前事務建立或修改的行。
    2、行的刪除操作的版本一定是未定義的或者大於當前事務的版本號,確定了當前事務開始之前,行沒有被刪除。
    符合了以上兩點則返回查詢結果。
  • INSERT
    InnoDB為每個新增行記錄當前系統版本號作為建立ID。“建立時間”=DB_ROW_ID,這時,“刪除時間 ”是未定義的;
  • DELETE
    InnoDB為每個刪除行的記錄當前系統版本號作為行的刪除ID。
  • UPDATE
    InnoDB複製了一行。這個新行的版本號使用了系統版本號。它也把系統版本號作為了刪除行的版本。

為了支援事務,Innbodb引入了下面幾個概念:

  • redo log
    redo log就是儲存執行的SQL語句到一個指定的Log檔案,當Mysql執行recovery時重新執行redo log記錄的SQL操作即可。當客戶端執行每條SQL(更新語句)時,redo log會被首先寫入log buffer;當客戶端執行COMMIT命令時,log buffer中的內容會被視情況重新整理到磁碟。redo log在磁碟上作為一個獨立的檔案存在,即Innodb的log檔案。
  • undo log
    與redo log相反,undo log是為回滾而用。具體內容就是copy事務前的資料庫內容(行)到undo buffer,在適合的時間把undo buffer中的內容重新整理到磁碟。undo buffer與redo buffer一樣,也是環形緩衝,但當緩衝滿的時候,undo buffer中的內容會也會被重新整理到磁碟;與redo log不同的是,磁碟上不存在單獨的undo log檔案,所有的undo log均存放在主ibd資料檔案中(表空間),即使客戶端設定了每表一個數據檔案也是如此。
  • rollback segment
    回滾段這個概念來自Oracle的事物模型,在Innodb中,undo log被劃分為多個段,具體某行的undo log就儲存在某個段中,稱為回滾段。可以認為undo log和回滾段是同一意思。
  • 鎖(前邊已有講述)
  • 隔離級別(前邊已有講述)

MVCC原理詳解

有事務插入persion表插入了一條新記錄:name為Jerry, age為24歲。可認為:隱式ID是1,事務ID和回滾指標,我們假設為NULL

事務1對該記錄的name做出修改,改為Tom

當事務1更改該行的值時,會進行如下操作:

  • 用排他鎖鎖定該行
  • 把該行資料拷貝到undo log中,作為舊記錄(即在undo log中有當前行的拷貝副本)
  • 拷貝完畢後,有如下操作:
    修改該行name為Tom;
    修改隱藏欄位的事務ID為當前事務1的ID(我們預設從1開始,之後遞增);
    回滾指標指向拷貝到undo log的副本記錄(即表示我的上一個版本就是它)。
  • 事務提交後,釋放鎖

事務2修改person表的同一個記錄,將age修改為30歲

當事務2更改該行的值時,會進行如下操作:

  • 用排他鎖鎖定該行
  • 把該行資料拷貝到undo log中,作為舊記錄。
    發現該行記錄已經有undo log了,那麼最新的舊資料作為連結串列的表頭,插在該行記錄的undo log最前面
  • 拷貝完畢後,有如下操作:
    修改該行age為30歲;
    修改隱藏欄位的事務ID為當前事務2的ID, 那就是2
    回滾指標指向剛剛拷貝到undo log的副本記錄
  • 事務提交後,釋放鎖

        從上面,我們就可以看出,不同事務或者相同事務的對同一記錄的修改,會導致該記錄的undo log成為一條記錄版本線性表,即:事務鏈,undo log的鏈首就是最新的舊記錄,鏈尾就是最早的舊記錄。 

        因此,如果undo log一直不刪除,則會通過當前記錄的回滾指標回溯到該行建立時的初始內容,所幸的時在Innodb中存在purge執行緒,它會查詢那些比現在最老的活動事務還早的undo log,並刪除它們,從而保證undo log檔案不至於無限增長。