深入理解 MySQL 事務 MVCC 的核心概念以及底層原理

語言: CN / TW / HK

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

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

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

1.1.當前讀的基本概念

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

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

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

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

1.開啟一個事務然後查詢xscjb中的數據,看到小明的ywcj是100mysql> 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 |

複製代碼

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 隱式字段。

[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",

複製代碼

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

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

目前有四個併發事務(事務 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 中記錄的舊數據對應的版本號,用於將來回滾使用。

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

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

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

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

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

最終我們可以看到在 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 版本鏈中的數據進行規則匹配,最終返回此次快照讀的數據。

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 四個字段的值,帶入到了規則中,下面開始匹配。

A)在匹配合適的版本數據時,首先匹配表中的記錄:

也就是這條數據,這條數據對應的 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 版本鏈中最上面的數據:

,該版本數據的 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 版本鏈中第二條版本數據:

,該版本數據的 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 四個字段帶入了新值。

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)首先匹配表中的記錄:

也就是這條數據,該版本數據的 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 版本鏈中最上面的數據:

,該版本數據的 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,也就意味着每次快照度產生的結構都是一樣的。

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 四個字段的值,帶入到了規則中,下面開始匹配。

A)首先匹配表中的記錄:

也就是這條數據,這條數據對應的 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 D 為 6) 之間,但是該版本的數據事務 ID 是 4,4 位於 m_ids (ID:3,4,5) 集合中。

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

B)然後匹配 undo log 版本鏈中最上面的數據:

,該版本數據的 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 版本鏈中第二條版本數據:

,該版本數據的 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 隔離級別下,首次快照讀讀的版本數據,在後續的快照讀中也會複用該數據,做到重複讀。

劃線

評論

複製