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

劃線

評論

複製