MYSQL---MVCC

通過本文將弄明白以下幾個問題。

一、思考

問題一:MVCC是什麼?主要爲了解決什麼問題?

問題二:MVCC的實現原理是什麼?

問題三:有了MVCC之後,開發者還需要做什麼?

二、分析 

2.1概念

該定義由此博文總結:https://draveness.me/database-concurrency-control

MVCC,即多版本併發控制,在這個機制中,每一個寫操作都會創建一個新版本的數據,每一個讀操作將從有限多個版本的數據中選擇一個最合適的返回,而管理和快速挑選數據的版本就是MVCC的工作。

其主要目的在於解決數據庫的“併發讀寫”能力。

2.2舉例

在博文【MYSQL---鎖】中,我們提到在更新數據時,InnoDB引擎會默認爲數據加X鎖,此時其它事務不能對該數據行加X鎖和S鎖,但是能讀取該數據,這便提高了數據庫的“併發讀寫”能力,如下:

假設有表t,字段(id,name);事務A和B。初始數據(1,'a');事務隔離級別爲RR。

事務A 事務B

SET autocommit=0;

BEGIN;

SET autocommit=0;

BEGIN;

 

//步驟一(獲取X鎖)

UPDATE t SET name='b' WHERE id=1;  //name=b

//步驟二(依然可讀,讀寫併發,MVCC控制)

SELECT name FROM t WHERE id=1; // name=1;

 

.....

COMMIT;

.....

COMMIT;

由上圖可知,事務B雖然擁有了數據行的X鎖,事務A雖然不能再加X鎖和S鎖,但是仍然可讀數據(這是RR隔離級別的特性,所以說可以說MVCC可以控制採用RR或者RC,請看後文分析),而且讀到的數據是事務開始時的數據,這便解決了“併發讀寫”問題,那MVCC到底是怎麼做的呢,請看下文分析。

2.3MVCC實現原理

要解決上述所說的“併發讀寫問題”,那麼事務A讀到的數據必然和事務B修改的數據不是同一份數據,在MVCC中,可以稱爲不是同一版本的數據。那麼就有兩個問題需要我們思考:

1.如何構建多版本?

2.事務A如何知道選擇哪個版本數據?

在看下文之前,您可以看一下這篇文章:https://juejin.im/post/5c68a4056fb9a049e063e0ab,其通俗易懂的解釋了多版本併發控制(MVCC)中的增刪查改操作。

InnoDB默認會給每行加三個字段

InndoDB在創建表時,會默認爲表增加三個字段,其中有兩個字段是與MVCC相關的:

DB_TRX_ID(下文中以tx_id表示):數據行的版本號,表示該條數據最後被修改時的事務id。

DB_ROLL_PT:刪除版本號,表示該條數據被刪除時的事務id。

快照

首先在事務系統中,會維護一個全局的活躍事務id(descriptors)。

當要創建一個快照(readview)會將上述的全局的活躍事務id拷貝一份到新建的快照(數據結構見下文)。當事務內根據條件查詢某條數據時,可能會查詢到數據的多個版本,這時對於查詢出來的每一條數據,都會根據快照判斷其是否滿足可見性(可以被當前事務看見),如果可以則返回,否則就利用undolog來構建歷史版本數據,直到構建到最老的版本或者可見性滿足。

所以快照的主要作用就是“數據可見性判斷”。

快照的數據結構(模擬):

readview{

int [] descriptors:數組,存儲數據庫中所有活躍事務(已經開始,但未提交;不包含只讀事務)的事務id,id從小到大排序。

int up_limit_id:descriptors數組中最小的值。

int low_limit_id:創建快照時產生的最大事務id(max_trx_id),該值一定大於descriptors數組中的最大事務id。

}

數據可見性判斷

有了前面介紹的兩個知識點,我們現在來分析一下,MVCC是如何判斷數據是否可見的。

其會用當前數據行的tx_id和快照中的up_limit_id和low_limit_id進行比較:

  1. 如果tx_id<up_limit_id:表示這條數據最後修改在快照被創建之前,因此可見。
  2. 如果tx_id>=low_limit_id,表示這條數據最後修改在快照被創建之後,因此不可見。
  3. 如果up_limit_id<=tx_id<low_limit_id,表示這條數據在快照創建之時,由其它活躍事務修改,因此不可見。
  4. 如果tx_id不在descriptors數組之中,經過前面的判斷,這種情況可能存在於 descriptors數組中最大值<tx_id<low_limit_id,因爲有了第三條,所以這裏仍然表示這條數據最後修改在快照被創建之前,因此可見。或者descriptors數組爲空,不能存在活躍性事務,那說明修改這條數據的事務已經全部提交,因此可見。

經過以上的判斷,如果仍然未找到可見性數據,則通過undolog去構建老版本數據直到找到可以被看見的數據或者undolog被解析完畢。

可重複讀、讀已提交與MVCC關係

其實可重複讀和讀已提交的根本不同在於數據可見性,“可重複讀”在事務提交之後,數據仍然不能被其他事務可見。而“讀已提交”,在事務提交之後,數據就能被其他事務看見。這裏根本原因是因爲MVCC實現中,創建快照的時機不同:

可重複讀(RR):快照在第一次查詢的時候創建,這個快照會一直持續到事務結束,期間數據可見性不會改變,所以在當前事務內不會產生數據不一致情況。

讀已提交(RC):事務中的每個查詢都會創建一個快照,這樣如果兩個查詢之間,有其他事務修改了數據,就導致數據可見性改變,就會產生數據不一致情況,所以不可重複讀。

通過MVCC再分析前面案例

上文案例如下:

背景:

假設有表t,字段(id,name);事務A和B。初始數據(1,'a');事務隔離級別爲RR。全局活躍事務id(descriptos)中爲空,此時該行數據情況如下:

 

id name tx_id DB_ROLL_PT
1 a 1(前面已經有事務id爲1的事務修改了該數據,但是已經提交) NULL

過程:

事務A和事務B執行步驟如下,假設事務A的id爲3,事務B的id爲2,即事務B先與事務A執行:
事務A 事務B

SET autocommit=0;

BEGIN;

SET autocommit=0;

BEGIN;

 

//步驟一(獲取X鎖)

UPDATE t SET name='b' WHERE id=1;  //name=b

//步驟二(依然可讀,讀寫併發,MVCC控制)

SELECT name FROM t WHERE id=1; // name=1;

 

.....

COMMIT;

.....

COMMIT;

分析:

1.當事務B執行步驟一之後:

該數據行情況:

id name tx_id DB_ROLL_PT
1 a 1 2(代表事務B刪除了數據)
1 b 2(事務B修改了數據) NULL
全局活躍事務id(descripotors數組)=[2],因爲事務B還未提交。

2.事務A執行步驟二:

事務A根據id=1查詢這條數據,先創建快照如下:

readview{

descripotors:[2,3],即事務A和事務B。

up_limit_id:2,descripotors數組中最小值。

low_limit_id:3,當前事務A的事務id。

}

根據id=1查詢到上述兩個版本數據,遍歷這兩條數據,按照上面的“可見性分析”規則,結果爲:

第一條數據滿足第一條:tx_id<up_limit_id,所以這條數據滿足可見性,返回。

通過上面的分析相信你已經可以解答開篇的幾個問題,其實弄懂原理之後,並不難。MVCC分析至此結束,如有不對之處,請指正。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

參考文章:

  1. 五分鐘搞懂MVCC機制:https://juejin.im/post/5c68a4056fb9a049e063e0ab
  2. MVCC多版本併發控制:https://segmentfault.com/a/1190000012650596
  3. 淘寶數據庫內核月報-InnoDB事務系統-MVCC:http://mysql.taobao.org/monthly/2017/12/01/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章