MVCC多版本併發控制核心概念以及底層原理

語言: CN / TW / HK

theme: smartblue

MVCC多版本併發控制核心概念以及底層原理

我正在參與掘金技術社區創作者簽約計劃招募活動,點擊鏈接報名投稿

1.當前讀與快照讀的基本概念

在MVCC多版本併發控制中,核心概念和原理是非常複雜的,我們先來搞清楚MVCC中常見名稱的基本概念,然後再來講解什麼是MVCC以及MVCC的原理。

1.1.當前讀的基本概念

當前讀指的是在事務中,通過Select查詢語句讀取的數據記錄是當前表中最新版本的記錄,默認情況下,在事務中讀取表中的數據時,為了避免併發事務對我們讀取的數據進行修改,會對讀取的記錄加鎖,即使其他事務修改了表中的數據,我們讀取到的數據仍然是其他事務修改之前的數據。

即使在事務中,我們也想要讀取當前表中最新的數據記錄,而並不是進入事務時查詢到的數據,那麼此時就需要用到當前讀的概念,突破事務一開始讀取數據的鎖,通過當前讀來讀取表中最新版本的數據記錄。

如何才能突破讀取表記錄加的鎖呢?很簡單隻要觸發當前讀的機制,使當前的查詢語句進化成當前讀的行為,就可以讀到表中最新版本的數據,當事務中執行的SQL,如select lock in share mode、update、insert、delete、select...for update這些,產生了共享鎖和排它鎖,此時就會產生當前讀。

下面來演示一下當前讀的效果。

```sql 1.開啟一個事務然後查詢xscjb中的數據,看到小明的ywcj是100 mysql> begin; mysql> select * from xscjb; +----+--------+------+------+------+------+ | xh | xm | ywcj | sxcj | yycj | pjcj | +----+--------+------+------+------+------+ | 1 | 小明 | 100 | 75 | 93 | NULL |

2.此時再開啟一個事務修改小明的ywcj並提交 mysql> begin; mysql> update xscjb set ywcj = '999' where xm = '小明'; mysql> commit;

3.此時第一個事務任然讀到的數據是當前事務進入時的數據狀態,並非是最新的數據

4.如果想要讀取表中最新的數據,那麼就需要通過產生共享鎖、排查鎖的方式讀取到 mysql> select * from xscjb lock in share mode; +----+--------+------+------+------+------+ | xh | xm | ywcj | sxcj | yycj | pjcj | +----+--------+------+------+------+------+ | 1 | 小明 | 999 | 75 | 93 | NULL | ```

image-20220627215456560

1.2.快照讀的基本概念

快照讀指的是:開啟事務後第一次查詢數據的結果集,這個結果集就會被做成快照讀,只要還是在當前的事務中,即使數據被其他事務修改了,我們無論執行多少次查詢,依舊查詢到的是快照讀的數據。

如1.1中的所示,即使ywcj被其他事務修改了,在當前事務中讀到的仍然是舊數據,也就是快照讀的數據。

簡單的select產生的都是快照讀,快照讀取的是記錄數據的可見版本,也有可能是歷史數據,不加鎖是非阻塞讀。

在不同隔離級別下,快照讀也不同:

  • Read Committed:每次select查詢,都會生產新的快照讀。
  • Repeatable Read:開啟事務後第一個select查詢,就是快照讀的地方。
  • Serializable:快照讀退化成當前讀。

快照讀也保證了數據的可重複讀。

2.什麼是MVCC多版本併發控制

MVCC全稱是Multi-Version Concurrency Control,多版本併發控制,MVCC可以維護一個數據的多個版本,使讀寫操作沒有衝突。

MVCC是一種併發控制的方法,有了MVCC的支持後,不再使用單純的行級鎖對數據庫中的併發進行控制,而是使用MVCC將數據庫中的行鎖與行的多個版本進行結合,只需要很小的開銷,就可以實現非鎖定讀,從而大大提高數據庫系統的併發性能。

併發控制也很好理解,有人通過事務讀取了表中的數據,同時也有人通過事務在表中寫入或修改了數據,就會導致多個人看到的數據是不一致的,通過併發控制的手段,使每個連接者,在某個瞬間看到的數據時一個快照,即使通過其他事務修改了表中的數據,對於讀者來説也是看不到的,從而保證數據的一致性。

MVCC實現的是讀寫不阻塞,讀寫互不影響,通俗一點來説,MVCC可以使用户覺得數據庫對於同一條數據,面對多個事務併發情況下,有多個不同版本的數據所提供。

MVCC多版本併發控制通過一定的機制生成一個數據請求時間點內,一致性的數據內容,也就是快照,並且利用這個快照來提供一定級別的一致性讀取,使用户讀寫數據互不影響。

MVCC實現原理依靠於三個部分:隱式字段、undo log日誌、ReadView。

3.MVCC多版本併發控制依賴的三個組件重要概念

我們知道什麼是MVCC之後,接下來就需要去探討MVCC多版本併發控制實現的原理了,再研究原理之前,先弄明白,MVCC依賴的隱式字段、undo log日誌、ReadView是什麼東西。

3.1.MySQL表中三個隱式字段的概念

當我們創建好一張數據表之後,除了表中所有的字段外,InnoDB存儲引擎還會添加上三個隱藏的字段。

  • DB_TRX_ID
  • 表中的數據時會被修改的,INSERT、UPDATE、DELETE這些語句默認情況下,一條就代表一個事務,這個字段就是來記錄最後一次修改本條數據的事務ID。
  • DB_ROLL_PTR
  • 該字段是指針,代表回滾指針,該字段值會記錄本條數據上次修改前的一個版本,每條數據被修改後都會在undo log中進行記錄,在undo log記錄的每條數據中都會有一個版本號,該字段就是來記錄本條數據上次修改前在undo log中的版本號,可以配合undo log日誌進行數據的回滾。
  • DB_ROW_ID
  • 該字段可能會出現也可能不出現,主要取決於表中是否存在主鍵,如果表中沒有主鍵,該字段就會出現,通過自增的方式為每條數據記錄一個ID,主要是為聚集索引服務的。

我們查看一個有主鍵的表所包含的隱式字段,當表中有主鍵時,只會出現DB_TRX_ID和DB_TRX_ID隱式字段。

sql [root@k8s-master ~]# ibd2sdi /var/lib/mysql/db_1/xscjb.ibd | grep name "name": "xscjb", "name": "xh", "name": "xm", "name": "ywcj", "name": "sxcj", "name": "yycj", "name": "pjcj", "name": "DB_TRX_ID", #記錄事務ID的隱式字段 "name": "DB_ROLL_PTR", #記錄回滾指針版本的隱式字段 "name": "PRIMARY", "name": "idx_xscjb_ywcj", "name": "db_1/xscjb", "filename": "./db_1/xscjb.ibd",

我們查看一個沒有主鍵的表所包含的隱式字段,當表中沒有主鍵時,三個隱式字段都會出現。

sql [root@k8s-master ~]# ibd2sdi /var/lib/mysql/db_1/jszx_xgymjzxxb.ibd | grep name "name": "jszx_xgymjzxxb", "name": "id", "name": "bm", "name": "name", "name": "xb", "name": "nl", "name": "szd", "name": "zjhm", "name": "wd", "name": "first_injection", "name": "second_injection", "name": "third_injection", "name": "wjzymjtyy", "name": "zhycjzymdsj", "name": "DB_ROW_ID", #記錄行ID的隱式字段 "name": "DB_TRX_ID", #記錄事務ID的隱式字段 "name": "DB_ROLL_PTR", #記錄回滾指針版本的隱式字段 "name": "PRIMARY", "name": "db_1/jszx_xgymjzxxb", "filename": "./db_1/jszx_xgymjzxxb.ibd",

3.2.undo log日誌以及版本鏈的概念

undo log是回滾日誌,當數據庫中產生insert、update、delete操作時就會產生便於數據回滾的日誌,該日誌就是undo log。

undo log日誌是可以被刪除的,當產生insert語句後,事務一旦提交,undo log中的insert語句就可以被立即刪除,因為undo log只會在回滾時用到,像update、delete語句則不會立即刪除,因為還有可能其他事務再讀取這些數據。

undo log日誌是實現MVCC版本控制最核心的一點,undo log日誌中的版本鏈為數據形成了一份不同內容版本的鏈,這些鏈都會記錄在undo log日誌文件中。

下面我們通過幾幅圖來演示undo log日誌中的版本鏈的概念。

有一張表的原始數據如下,表中只有一條記錄,DB_TRX_ID字段的值為1,因為是新表只有一條數據,那麼對應的事務ID也就是1,DB_ROLL_PTR字段的值為NULL,新插入的數據沒有被更新過,因此該字段的值為null。

| id | age | name | DB_TRX_ID | DB_ROLL_PTR | | ---- | ---- | ---- | --------- | ----------- | | 30 | 30 | A30 | 1 | null |

目前有四個併發事務(事務1是插入了這條數據,從事務2開始)同時操作這張表中的數據,我們來觀察undo log會記錄什麼。

(undo log中記錄的是sql語句,這裏為了方便演示,以真實數據代替)

1)事務2:修改表中id為30的數據,將age的值修改為3,修改完成後提交事務。

如下圖所示,當事務2中的修改語句執行時,首先將舊數據記錄在undo log日誌中,然後再去更新表中的記錄,並且更新表中的數據時,會將隱式字段DB_TRX_ID的值更新成事務2的ID,同時回滾指針字段DB_ROLL_PTR的值也會指向undo log中記錄的舊數據對應的版本號,用於將來回滾使用。

image-20220627225951095

2)事務3:修改表中id為30的數據,將name字段修改為A3,修改完成後提交事務。

首先也是將變更前的數據記錄到undo log日誌文件中,此時的版本鏈不變,記錄好之後,開始修改表中的數據,同時會將本條數據的DB_TRX_ID字段值修改成最後一次事務的ID,DB_ROLL_PTR的值會修改成undo log中記錄的修改前的舊數據對應的版本號,此時鏈就發生了改變了,表中數據的DB_ROLL_PTR值指向了最新一次舊數據的版本號,那麼undo log中最新一次舊數據表的版本號同樣也會指向它上一次舊數據對應的版本號。

新數據指向最新一次舊數據的版本號,最新一次舊數據指向上次舊數據的版本號。

image-20220627230354294

3)事務4:修改表中id為30的數據,將age字段修改為10,修改完成後提交事務。

此時版本鏈的編號和事務3基本一樣了,首先在undo log中記錄舊數據,然後修改新數據的內容,然後將DB_TRX_ID字段修改成最新事務的ID,將DB_ROLL_PTR指針指向上一次舊數據對應的版本號,undo log中的最新一次的舊數據,也會指向上一次舊數據對應的版本號。

image-20220627230758168

最終我們可以看到在undo log中已經形成版本鏈了,不同事務或者相同事務操作一條記錄時,會在undo log中為這條記錄生產版本鏈表,鏈表的頭部是最新的舊數據記錄,鏈表的底部是最早的舊數據記錄。

此刻我們的一條數據就對應了很多個不同版本的數據情況,那麼如何來識別讀哪一個版本的數據呢?就需要去了解ReadView了。

3.3.ReadView讀視圖的概念

ReadView讀視圖:是SQL產生了快照讀時,生成一個ReadView作為MVCC讀取數據的依據,我們知道當產生快照讀時,讀到的幾乎都是歷史數據,並不是最新數據,在undo log中記錄的每一條舊數據記錄都是歷史數據,一條數據可能會對應很多個版本的歷史數據,那麼快照讀在執行時究竟應該讀取哪一個版本的數據呢?其實就是靠ReadView讀視圖來決定的。

在ReadView中有四個字段來記錄不同類型事務的ID,並且這四個字段與undo log中的事務ID有相應的匹配規則,當滿足某一項規則時,就會讀取該規則對應的版本的歷史數據。

ReadView讀事務會依據以下四個字段判斷要讀取那一個版本的歷史數據:

  • m_ids:是一個集合,當前活躍的所有讀寫事務的事務ID都會記錄在這個ids集合中。
  • 活躍的事務就表示當前事務正在進行中,還沒有提交,只要事務沒有提交都會處於活躍的狀態。

  • min_trx_id:最小活躍事務ID。

  • 是基於m_ids集合內的所有活躍的事務ID,在這個集合中最小的活躍事務ID。

  • max_trx_id:預分配事務ID,不是當前活躍的事務集合中最大的事務ID,相當於一個預留的事務ID,一般都是當前事務ID+1的新事務ID。

  • creator_trx_id:ReadView創建者的事務ID,通常情況也是當前事務的事務ID。

當前數據中DB_TRX_ID字段對應的事務ID,如果比最小的活躍事務ID還要小,就表示DB_TRX_ID字段對應的事務ID是處於提交的狀態。如果DB_TRX_ID字段對應的事務ID比最小活躍事務ID還大,那麼説明改事務可能也是活躍事務處於未提交的狀態。

有了讀取數據版本的依據之後,就需要有對應的規則來決定要讀取數據的哪個版本。

ReadView決定要讀取數據的哪個歷史版本時,是由undo log版本鏈中數據對應的事務ID與ReadView的四個字段中記錄的事務ID進行規則匹配,共有四種匹配規則,當匹配的結果滿足規則時,就會讀取規則對應的歷史版本數據。

undo log版本鏈中針對相同的數據可能會記錄很多條不同版本的數據,此時就會先拿表中當前的數據與ReadView規則進行匹配,如果滿足則讀取這個版本的數據,如果不滿足則再去undo log版本鏈中從上往下一次匹配每一條數據,當滿足ReadView規則時,則讀取對應版本的數據,

在匹配ReadView規則時,是從上往下依次進行規則匹配的,當滿足第一個規則時就讀取對應的版本數據,後面的規則將不會匹配。讀取數據肯定會被某一個規則所匹配。

ReadView讀取某個版本數據時的四種匹配規則:

表中的一條數據或者undo log版本鏈中的一條數據,每一條數據都稱為一個版本。匹配規則是先從表中的數據記錄開始匹配,如果表中數據不滿足規則時,再從undo log版本鏈中對於數據的多個版本,從上到下依次匹配,只要有一個版本滿足了規則,則會返回該版本的數據,不會再往下進行匹配。

  • 當trx_id == creator_trx_id

  • 當某個版本的數據記錄中DB_TRX_ID字段記錄的事務ID,與ReadView的creator_trx_id字段記錄的事務ID相同,那麼就可以讀取這個版本中的數據。

  • 因為creator_trx_id字段的值記錄的是當前事務的ID,如果數據中的記錄最後一次操作的事務ID值與creator_trx_id字段的值相同,就表示是當前事務所修改的數據,因此它是可以讀取到最新版本的數據的。
  • 當trx_id < min_trx_id

  • 當某個版本的數據中記錄事務ID值,小於所有活躍事務中最小的事務ID值時,就可以讀取這個版本的數據。

  • 因為所有的活躍事務,不管ID是大還是小,都是活躍是未提交的事務,如果當前版本的數據事務ID與活躍的事務ID相等或者比它ID大,就説明這個版本的數據還有可能被其他的事務處理中,因此是不可以被訪問到的。只有當版本中數據的事務ID比所有活躍事務中最小的那個事務ID還要小,就表示該版本的數據沒有被事務使用了,已經是提交狀態了,因此就可以讀取這個版本的數據。
  • 當trx_id > max_trx_id

  • 當某個版本的數據中記錄事務ID值,大於預留的事務ID,那麼就不可以讀取這個版本中的數據。

  • 如果當前版本中數據記錄的事務ID比預分配的事務ID要大,那麼就説明這個版本的數據是在我們當前事務之後又開啟的新事務,數據在處理中,因此不可以讀取這個版本中的數據。
  • 當min_trx_id <= trx_id <= max_trx_id

  • 當某個版本的數據記錄中事務ID的值,大於最小活躍的事務ID值,也小於預留的事務ID值,也就是事務ID位於最小活躍事務ID和預留事務ID之間的ID,並且當前版本數據記錄中的事務ID值不在m_ids集合中,當滿足這個規則時,那麼就可以讀取這個版本中的數據。

  • 這個規則相當於給了一個事務ID的範圍,最小事務ID---最大事務ID區間的事務ID,如果版本中數據的事務ID在這個範圍列表裏,還需要看一下這個事務ID是不是活躍的事務ID,主要是在m_ids集合中查看,如果也不會活躍的ID,那麼就允許讀取此版本的數據,否則將不允許。

在四種規則裏,trx_id表示的是當前版本中數據對應的事務ID的值,要通過trx_id的值和ReadView四個字段對應的值,進行匹配,滿足相應規則時放行。

在不同的隔離級別下,生成ReadView的時機不同:

  • READ COMMITTED :在事務中每一次執行快照讀時都生成一個ReadView,每個ReadView中四個字段的值都是不同的。

  • REPEATABLE READ:僅在事務中第一次執行快照讀時生成ReadView,後續複用該ReadView。

4.MVCC實現多版本併發控制的原理

MVCC多版本併發控制實現的原理就是通過InnoDB表的隱藏字段、undo log版本鏈、ReadView讀視圖配合來實現。

1)首先在表中的數據都會有兩個主要的隱藏字段,一個是DB_TRX_ID記錄最後一次操作的事務ID,還有一個是DB_ROLL_PTR記錄版本指針用於回滾時使用。

2)在多併發事務的場景下,不同事務操作完表數據會都會將舊數據記錄在undo log,一條數據在undo log日誌中可能會多個不同版本的數據,最終形成版本鏈表,不同版本的數據通過DB_ROLL_PTR字段值相互關聯。

3)當數據在undo log日誌中形成版本鏈之後,MVCC就會通過ReadView讀視圖根據四個字段,與undo log版本鏈中不同版本中數據的事務ID進行規則匹配,當undo log版本鏈中從上到下的某一個版本數據滿足ReadView讀視圖中的規則,那麼就讀取該版本對應的數據,並且不會再使用版本鏈中其他的版本數據再進行規則匹配。

簡單來説,MVCC實現多版本併發控制的原理,就是根據多事務在undo log中產生的多條舊數據形成的版本鏈表,將一條數據的多個版本中的事務ID與ReadView讀視圖中的四個字段所對應的事務ID進行規則匹配,如果這個版本的數據滿足ReadView四個字段的規則,那麼就讀取這個版本的數據,如果不滿足規則,則用另一個版本的數據依次進行匹配,知道讀到滿足規則的數據。

多版本併發控制就是在多事務的場景下,讀取針對當前事務最合適的一個版本的數據,可能是新數據也可能是舊數據。

MVCC+鎖就實現了事務的隔離性,事務的一致性由ReadLog和UndoLog保證。

5.不同隔離級別下MVCC實現併發控制的原理

5.1.RC隔離級別下MVCC多版本併發控制的原理分析

在RC隔離級別下,每當執行的SQL是快照讀類型的,就會生成一個ReadView讀視圖,每次生成的ReadView讀視圖所對應的四個字段值都是不同的,在RC隔離級別下,每次快照讀讀取的版本數據可能也不相同。

下面我們通過一組事務來分析RC隔離級別下MVCC多版本併發控制的原理。

如下圖所示,在併發事務5中,查詢了兩次id為30的數據,由於當前的隔離級別是RC,所以每當產生一次快照讀都會生成一個ReadView,每次生成的ReadView四個字段值都不同,也就意味着兩次查詢相同數據的結果可能都不相同。兩次快照讀在獲取數據時,會根據所生成的ReadView四個字段的值與undolog版本鏈中的數據進行規則匹配,最終返回此次快照讀的數據。

image-20220629105505177

ReadView四個字段的獲取的值:m_ids記錄所有活躍事務的id號,分別是3/4/5對應事務3-事務5,min_trx_id記錄所有活躍事務中事務ID最小的值,那麼也就是3,max_trx_id是預留的事務ID,當前事務ID是5,那麼預留的事務ID就是6,creator_trx_id是生成readview的事務id,也就是5。

1)分析事務5中第一次快照讀的MVCC多版本併發控制的原理流程

如下圖所示,左側是快照讀可能會讀取的各個版本的數據,有表中的記錄,有undo log版本鏈中的記錄,右側是ReadView讀取版本數據的規則,並且將第一個快照讀產生的ReadView四個字段的值,帶入到了規則中,下面開始匹配。

image-20220629112449949

A)在匹配合適的版本數據時,首先匹配表中的記錄:image-20220629112802451也就是這條數據,這條數據對應的trx_id事務id是4,此時MVCC就會通過ReadView帶着這條數據去ReadView規則中進行匹配,在第一條規則中,trx_id為4不等於creator_trx_id(ID為5)的ID值,第二條規則中,trx_id=4大於了min_trx_id(ID為3),第三條規則中trx_id=4小於了max_trx_id(ID為6),第四條規則,trx_id=4位於min_trx_id(ID為3)與max_trx_id(ID為6)之間,但是該版本的數據事務ID是4,4位於m_ids(ID:3,4,5)集合中。

規則匹配結果為:1)不滿足 2)不滿足 3)不滿足 4)不滿足,該版本的數據都不滿足規則,此時就要去undo log版本鏈中匹配下一條數據了。

表中數據不滿足了,此時從undo log鏈中從上往下挨個匹配每個版本的數據,當某一個版本數據滿足規則後,下面的數據不再進行匹配。

B)然後匹配undo log版本鏈中最上面的數據:image-20220629113818189,該版本數據的trx_id事務ID為3,將trx_id=3帶入右側的ReadView版本鏈中進行匹配,在第一條規則中,trx_id為3不等於creator_trx_id(ID為5)的ID值,第二條規則中,trx_id=3等於了min_trx_id(ID為3),第三條規則中trx_id=3小於了max_trx_id(ID為6),第四條規則,trx_id=3位於min_trx_id(ID為3)與max_trx_id(ID為6)之間,但是該版本的數據事務ID是3,3位於m_ids(ID:3,4,5)集合中。

規則匹配結果為:1)不滿足 2)不滿足 3)不滿足 4)不滿足,該版本的數據都不滿足規則,此時繼續從undo log版本鏈中從上到下匹配下一條數據。

C)接着匹配undo log版本鏈中第二條版本數據:image-20220629114122695,該版本數據的trx_id事務ID為2,將trx_id=2帶入右側的ReadView版本鏈中進行匹配,在第一條規則中,trx_id為2不等於creator_trx_id(ID為5)的ID值,第二條規則中,trx_id=2小於min_trx_id(ID為3),該版本的數據滿足ReadView規則中的第二個規則,此時就會終止匹配,快照讀此時就會返回版本鏈中這個版本所對應的數據。

表中記錄、undo log版本鏈的數據從上往下依次匹配ReadView規則,當有一個版本的數據滿足規則後,就返回給快照讀獲取該版本的數據,這就是MVCC多版本併發情況下,分配給快照讀合適版本數據的原理和過程。

2)分析事務5中第二次快照讀的MVCC多版本併發控制的原理流程

在第一次快照讀時我們已經理解了MVCC是如何實現多版本併發控制的,根據表中記錄、undo log版本鏈中多個不同版本的數據,按照數據中的事務ID在ReadView規則中進行匹配,當滿足規則時,將該版本的數據返回給快照讀。

第二次快照度和第一次快照度大差不差,在第二次快照讀時,事務3提交了,那麼在活躍的事務中就沒有事務3了,數據還是左側這麼多個版本,右側的規則中為此次生成的ReadView四個字段帶入了新值。

image-20220629115002783

ReadView四個字段的獲取的值:m_ids記錄所有活躍事務的id號,分別是4/5對應事務4-事務5,min_trx_id記錄所有活躍事務中事務ID最小的值,那麼也就是4,max_trx_id是預留的事務ID,當前事務ID是5,那麼預留的事務ID就是6,creator_trx_id是生成readview的事務id,也就是5。

A)首先匹配表中的記錄:image-20220629112802451也就是這條數據,該版本數據的trx_id事務ID為4,將trx_id=3帶入右側的ReadView版本鏈中進行匹配,在第一條規則中,trx_id為4不等於creator_trx_id(ID為5)的ID值,第二條規則中,trx_id=4等於了min_trx_id(ID為4),第三條規則中trx_id=4小於了max_trx_id(ID為6),第四條規則,trx_id=4位於min_trx_id(ID為4)與max_trx_id(ID為6)之間,但是該版本的數據事務ID是4,4位於m_ids(ID:4,5)集合中。

規則匹配結果為:1)不滿足 2)不滿足 3)不滿足 4)不滿足,該版本的數據都不滿足規則,此時就要去undo log版本鏈中匹配下一條數據了。

B)然後匹配undo log版本鏈中最上面的數據:image-20220629113818189,該版本數據的trx_id事務ID為3,將trx_id=3帶入右側的ReadView版本鏈中進行匹配,在第一條規則中,trx_id為3不等於creator_trx_id(ID為5)的ID值,第二條規則中,trx_id=3小於min_trx_id(ID為4),該版本的數據滿足ReadView規則中的第二個規則,此時就會終止匹配,快照讀此時就會返回版本鏈中這個版本所對應的數據。

5.2.RR隔離級別下MVCC多版本併發控制的原理分析

在RR隔離級別下,只會在事務第一次執行快照讀時會生成一個ReadView讀視圖,後續快照讀都會複用這個ReadView,讀取的版本數據都是相同的,也就説明了RR隔離級別是可重複度。

下面我們通過一組事務來分析RR隔離級別下MVCC多版本併發控制的原理。

如下圖所示,在併發事務5中,查詢了兩次id為30的數據,由於當前的隔離級別是RR,所以當第一次產生快照讀會生成一個ReadView決定四個字段的值,後面再有快照讀執行時,就會複用第一次快照讀產生的ReadView,也就意味着每次快照度產生的結構都是一樣的。

image-20220629131209342

ReadView四個字段的獲取的值:m_ids記錄所有活躍事務的id號,分別是3/4/5對應事務3-事務5,min_trx_id記錄所有活躍事務中事務ID最小的值,那麼也就是3,max_trx_id是預留的事務ID,當前事務ID是5,那麼預留的事務ID就是6,creator_trx_id是生成readview的事務id,也就是5。

多個版本的數據在RR隔離級別下的規則匹配流程與RC隔離級別一致。

1)分析事務5中首次快照讀的MVCC多版本併發控制的原理流程

如下圖所示,左側是快照讀可能會讀取的各個版本的數據,有表中的記錄,有undo log版本鏈中的記錄,右側是ReadView讀取版本數據的規則,並且將第一個快照讀產生的ReadView四個字段的值,帶入到了規則中,下面開始匹配。

image-20220629112449949

A)首先匹配表中的記錄:image-20220629112802451也就是這條數據,這條數據對應的trx_id事務id是4,此時MVCC就會通過ReadView帶着這條數據去ReadView規則中進行匹配,在第一條規則中,trx_id為4不等於creator_trx_id(ID為5)的ID值,第二條規則中,trx_id=4大於了min_trx_id(ID為3),第三條規則中trx_id=4小於了max_trx_id(ID為6),第四條規則,trx_id=4位於min_trx_id(ID為3)與max_trx_id(ID為6)之間,但是該版本的數據事務ID是4,4位於m_ids(ID:3,4,5)集合中。

規則匹配結果為:1)不滿足 2)不滿足 3)不滿足 4)不滿足,該版本的數據都不滿足規則,此時就要去undo log版本鏈中匹配下一條數據了。

B)然後匹配undo log版本鏈中最上面的數據:image-20220629113818189,該版本數據的trx_id事務ID為3,將trx_id=3帶入右側的ReadView版本鏈中進行匹配,在第一條規則中,trx_id為3不等於creator_trx_id(ID為5)的ID值,第二條規則中,trx_id=3等於了min_trx_id(ID為3),第三條規則中trx_id=3小於了max_trx_id(ID為6),第四條規則,trx_id=3位於min_trx_id(ID為3)與max_trx_id(ID為6)之間,但是該版本的數據事務ID是3,3位於m_ids(ID:3,4,5)集合中。

規則匹配結果為:1)不滿足 2)不滿足 3)不滿足 4)不滿足,該版本的數據都不滿足規則,此時繼續從undo log版本鏈中從上到下匹配下一條數據。

C)接着匹配undo log版本鏈中第二條版本數據:image-20220629114122695,該版本數據的trx_id事務ID為2,將trx_id=2帶入右側的ReadView版本鏈中進行匹配,在第一條規則中,trx_id為2不等於creator_trx_id(ID為5)的ID值,第二條規則中,trx_id=2小於min_trx_id(ID為3),該版本的數據滿足ReadView規則中的第二個規則,此時就會終止匹配,快照讀此時就會返回版本鏈中這個版本所對應的數據。

在RR隔離級別下,首次快照讀讀的版本數據,在後續的快照讀中也會複用該數據,做到重複讀。