MVCC多版本併發控制核心概念以及底層原理
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 | ```
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
[[email protected] ~]# 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
[[email protected] ~]# 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中記錄的舊資料對應的版本號,用於將來回滾使用。
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(ID為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隔離級別下,首次快照讀讀的版本資料,在後續的快照讀中也會複用該資料,做到重複讀。