- GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯系小編并注明來源,
- GreatSQL是MySQL的國產分支版本,使用上與MySQL一致,
-
- 前情提要
- 當前讀
- 快照讀
- 什么是MVCC
- 三個隱藏欄位
- Undo Log回滾日志
- MVCC版本鏈
- ReadView讀視圖
- 不同隔離級別下MVCC分析
- READ-COMMITTED隔離級別
- REPEATABLE-READ隔離級別
前情提要
事務有四大特性ACID分別是:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)
其中隔離性是通過資料庫的鎖加上MVCC(多版本并發控制)來保證的,
在介紹MVCC之前先來了解一下當前讀和快照讀,
當前讀
當前讀讀取的是記錄的最新版本,同時在讀取的時候還要保證其他的并發事務不能更改當前記錄,那么當前讀會對它要讀取的記錄進行加鎖,不同的操作會加上不同型別的鎖,如:SELECT ... LOCK IN SHARE MODE(共享鎖),SELECT ... FOR UPDATE、UPDATE、INSERT、 DELETE(排他鎖),
快照讀
簡單的不加鎖的SELECT就是快照讀,快照讀讀取的是快照生成時的資料,不一定是最新的資料,它是不加鎖的非阻塞讀,而不同隔離級別下,創建快照的時機也不同:
READ-COMMITTED(讀已提交):事務每次SELECT時創建ReadViewREPEATABLE-READ(可重復讀):事務第一次SELECT時創建ReadView,后續一直使用
在MySQL默認隔離級別(REPEATABLE-READ)下,快照讀保證了資料的可重復讀,
什么是MVCC
MVCC全稱Multi-Version Concurrency Control,即多版本并發控制,它是一種并發控制的方法,它可以維護一個資料的多個版本,用更好的方式去處理讀寫沖突,做到即使有讀寫沖突也能不加鎖,MySQL中MVCC的具體實作,還需要依賴于表中的三個隱藏欄位、Undo Log日志以及ReadView,
三個隱藏欄位
mysql> SHOW CREATE TABLE stu \G;
*************************** 1. row ***************************
Table: stu
Create Table: CREATE TABLE `stu` (
`id` int NOT NULL,
`name` varchar(10) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
mysql> SELECT * FROM stu;
+----+--------+
| id | name |
+----+--------+
| 1 | m |
| 2 | f |
+----+--------+
當創建了上述這張表后,我們在查看表結構時只能看到id、name欄位,實際上除了這兩個欄位外,InnoDB引擎還自動為我們添加了三個隱藏欄位,見下表:
| 欄位 | 含義 |
|---|---|
| DB_TRX_ID | 最近修改事務ID,記錄插入這條記錄或最后一次修改該記錄的事務ID, |
| DB_ROLL_PTR | 回滾指標,指向這條記錄的上一個版本,用于配合Undo Log,指向上一個版本, |
| DB_ROW_ID | 隱藏主鍵,如果表結構沒有指定主鍵,將會生成該隱藏欄位, |
我們可以使用ibd2sdi工具來從表空間檔案中提取序列化的字典資訊(SDI),來驗證一下這三個隱藏欄位是否存在,
["ibd2sdi"
,
{
"type": 1,
"id": 402,
"object":
{
"mysqld_version_id": 80025,
"dd_version": 80023,
"sdi_version": 80019,
"dd_object_type": "Table",
"dd_object": {
"name": "stu",
"mysql_version_id": 80025,
"created": 20220919023413,
"last_altered": 20220919023413,
"hidden": 1,
"options": "avg_row_length=0;encrypt_type=N;explicit_encryption=0;key_block_size=0;keys_disabled=0;pack_record=1;stats_auto_recalc=0;stats_sample_pages=0;",
"columns": [
{
"name": "id",
"type": 4,
"is_nullable": false,
"is_zerofill": false,
"is_unsigned": false,
"is_auto_increment": false,
"is_virtual": false,
"hidden": 1,
···省略
},
{
"name": "name",
"type": 16,
"is_nullable": false,
"is_zerofill": false,
"is_unsigned": false,
"is_auto_increment": false,
"is_virtual": false,
"hidden": 1,
···省略
},
{
"name": "DB_TRX_ID", #最近修改事務ID
"type": 10,
"is_nullable": false,
"is_zerofill": false,
"is_unsigned": false,
"is_auto_increment": false,
"is_virtual": false,
"hidden": 2,
···省略
},
{
"name": "DB_ROLL_PTR", #回滾指標
"type": 9,
"is_nullable": false,
"is_zerofill": false,
"is_unsigned": false,
"is_auto_increment": false,
"is_virtual": false,
"hidden": 2,
···省略
}
],
注意:因為這張表里已經指定了主鍵為id列,所以不會生成隱藏主鍵DB_ROW_ID列,
Undo Log回滾日志
回滾日志,在增、改、刪操作的時候產生的便于資料回滾的日志,當INSERT操作的時候,產生的回滾日志在事務提交后可被立即洗掉,而UPDATE和DELETE操作的時候,產生的Undo Log日志不僅在進行資料回滾時需要,在進行快照讀時也需要,所以不會立即被洗掉,
Undo Log詳情可見文章:待浩源Undo Log文章發表后添加
MVCC版本鏈
當有多個并發事務操作一行資料時,對這行資料的修改會產生多個版本,多個版本通過上述的一個隱藏欄位DB_ROLL_PTR回滾指標指向Undo Log資料地址形成一個鏈表,即MVCC版本鏈,

ReadView讀視圖
ReadView讀視圖是快照讀SQL執行時MVCC提取資料的依據,記錄并維護系統當前活躍的事務(未提交的)id,
上面講過Undo Log和MVCC版本鏈,一條資料經過多次修改會產生多個版本,而快照讀是根據不同時機創建的快斬訓取資料的,那么快照讀SQL在執行時該讀取那個版本的資料就是靠ReadViw讀視圖來決定的,
ReadView讀視圖中包含了四個核心欄位,也是讀取資料的判斷依據:
| 欄位 | 含義 |
|---|---|
| m_ids | 當前活躍的事務ID集合 |
| min_trx_id | 最小活躍事務ID |
| max_trx_id | 預分配事務ID,當前最大事務ID+1(因為事務ID是自增的) |
| creator_trx_id | ReadView創建者的事務ID |
ReadView一共有四種匹配規則:
| 條件 | 能否訪問 | 說明 |
|---|---|---|
| trx_id == creatro_trx_id | 可以訪問該版本 | 成立,說明資料是當前這個事務更改的, |
| trx_id < min_trx_id | 可以訪問該版本 | 成立,說明資料已經提交了, |
| trx_id > max_trx_id | 不可以訪問該版本 | 成立,說明該事務是在ReadView生成后才開啟的, |
| min_trx_id <= trx_id <= max_trx_id | 如果trx_id不在m_ids中,那么可以訪問該版本 | 成立,說明資料已經提交, |
不同隔離級別下MVCC分析
READ-COMMITTED隔離級別
前面有提到過在READ-COMMITTED隔離級別下事務在每次快照讀SQL執行時創建ReadView,每次創建的ReadView的四個欄位對應的值也是不同的,所以在READ-COMMITTED隔離級別下每次快照讀SQL獲取的資料可能也是不同的,
下面通過一個READ-COMMITTED隔離級別下并發事務的案例來詳細看看:
現有四個并發事務同時訪問一條資料:


在上述并發事務中,事務5查詢了兩次id為1的資料,因為當前的隔離級別設定為了READ-COMMITTED,事務在每次快照讀SQL執行時創建一個ReadView,每次生成的ReadView中的四個欄位值都不同,那么三次快照讀都會根據生成的ReadView中的欄位進行規則匹配,從而決定回傳的資料,接下來看看流程:
事務5第一次快照讀解讀
事務5第一次進行查詢時生成的ReadView以及原資料如下圖:

在匹配版本資料前,先與表中資料進行匹配:
該資料對應的DB_TRX_ID為3,此時MVCC就會通過ReadView帶著這條資料去進行規則匹配:
首先是第一條規則db_trx_id == creator_trx_id,db_trx_id(3)不等于creator_trx_id(5)故不成立;
第二條規則db_trx_id < min_trx_id,db_trx_id(3)不小于min_trx_id(3)故不成立;
第三條規則db_trx_id > max_trx_id,db_trx_id(3)小于max_trx_id(6)故不成立;
第四條規則min_trx_id <= db_trx_id <= max_trx_id,db_trx_id(3)在min_trx(3)與max_trx_id(6)之間,但是同時處于m_ids(3,4,5)集合之中故也不成立,
經過這次匹配,表中最新的資料無法匹配,故要與MVCC版本鏈中最上面的資料進行規則匹配
與MVCC版本鏈中最上方的版本進行匹配:

第一條規則db_trx_id(2)不等于creator_trx_id(5)故不成立;
第二條規則db_trx_id(2)小于min_trx_id(3),該版本的資料滿足匹配規則中的第二條,說明資料已經提交,此時匹配將終止并回傳這個版本對應的資料,
事務5第二次快照讀
因為當前事務的隔離級別為READ-COMMITTED(讀已提交),所以在每次快照讀的時候都會創建一個ReadView,所以事務5第二次進行查詢時生成的ReadView以及原資料如下圖:

在匹配版本資料前,先與表中資料進行匹配:
該資料對應的DB_TRX_ID為4,此時MVCC就會通過ReadView帶著這條資料去進行規則匹配:
首先是第一條規則db_trx_id == creator_trx_id,db_trx_id(4)不等于creator_trx_id(5)故不成立;
第二條規則db_trx_id < min_trx_id,db_trx_id(4)不小于min_trx_id(4)故不成立;
第三條規則db_trx_id > max_trx_id,db_trx_id(4)小于max_trx_id(6)故不成立;
第四條規則min_trx_id <= db_trx_id <= max_trx_id,db_trx_id(4)在min_trx(4)與max_trx_id(6)之間,但是同時處于m_ids(4,5)集合之中故也不成立,
經過這次匹配,表中最新的資料無法匹配,故要與MVCC版本鏈中最上面的資料進行規則匹配
與MVCC版本鏈中最上方的版本進行匹配:

第一條規則db_trx_id(3)不等于creator_trx_id(5)故不成立;
第二條規則db_trx_id(3)小于min_trx_id(4),該版本的資料滿足匹配規則中的第二條,說明資料已經提交,此時匹配將終止并回傳這個版本對應的資料,
REPEATABLE-READ級別
現在來看看REPEATABLE-READ可重復讀隔離級別有什么不同的地方,同樣,有四個并發事務同時訪問一條資料:


在上述并發事務中,事務5查詢了兩次id為1的資料,因為當前的隔離級別設定為了REPEATABLE-READ,事務在第一次快照讀SQL執行時創建ReadView,后續該事務所有的快照讀都復用該ReadView,接下來看看流程:
事務5第一次快照讀解讀
事務5第一次進行查詢時生成的ReadView以及原資料如下圖:

在匹配版本資料前,先與表中資料進行匹配:
該資料對應的DB_TRX_ID為3,此時MVCC就會通過ReadView帶著這條資料去進行規則匹配:
首先是第一條規則db_trx_id == creator_trx_id,db_trx_id(3)不等于creator_trx_id(5)故不成立;
第二條規則db_trx_id < min_trx_id,db_trx_id(3)不小于min_trx_id(3)故不成立;
第三條規則db_trx_id > max_trx_id,db_trx_id(3)小于max_trx_id(6)故不成立;
第四條規則min_trx_id <= db_trx_id <= max_trx_id,db_trx_id(3)在min_trx(3)與max_trx_id(6)之間,但是同時處于m_ids(3,4,5)集合之中故也不成立,
經過這次匹配,表中最新的資料無法匹配,故要與MVCC版本鏈中最上面的資料進行規則匹配
與MVCC版本鏈中最上方的版本進行匹配:

第一條規則db_trx_id(2)不等于creator_trx_id(5)故不成立;
第二條規則db_trx_id(2)小于min_trx_id(3),該版本的資料滿足匹配規則中的第二條,說明資料已經提交,此時匹配將終止并回傳這個版本對應的資料,
事務5第二次快照讀解讀
因為當前事務的隔離級別為REPEATABLE-READ(可重復讀),所以第二次快照讀也會沿用第一次快照讀時創建的ReadView,如下:

在匹配版本資料前,先與表中資料進行匹配:
該資料對應的DB_TRX_ID為4,此時MVCC就會通過ReadView帶著這條資料去進行規則匹配:
首先是第一條規則db_trx_id == creator_trx_id,db_trx_id(4)不等于creator_trx_id(5)故不成立;
第二條規則db_trx_id < min_trx_id,db_trx_id(4)不小于min_trx_id(3)故不成立;
第三條規則db_trx_id > max_trx_id,db_trx_id(4)小于max_trx_id(6)故不成立;
第四條規則min_trx_id <= db_trx_id <= max_trx_id,db_trx_id(4)在min_trx(4)與max_trx_id(6)之間,但是同時處于m_ids(4,5)集合之中故也不成立,
經過這次匹配,表中最新的資料無法匹配,故要與MVCC版本鏈中最上面的資料進行規則匹配
與MVCC版本鏈中最上方的版本進行匹配:

第一條規則db_trx_id(3)不等于creator_trx_id(5)故不成立;
第二條規則db_trx_id(3)不小于min_trx_id(4)故不成立;
第三條規則db_trx_id小于max_trx_id(6)故不成立;
第四條規則db_trx_id(3)在min_trx(3)與max_trx_id(6)之間,但是同時處于m_ids(3,4,5)集合之中故也不成立,
經過第二次匹配,MVCC版本鏈中最上層的資料版本也無法匹配,故要與第二條版本進行匹配
與MVCC版本鏈中第二條版本進行匹配:

第一條規則db_trx_id(2)不等于creator_trx_id(5)故不成立;
第二條規則db_trx_id(2)小于min_trx_id(3),該版本的資料滿足匹配規則中的第二條,說明資料已經提交,此時匹配將終止并回傳這個版本對應的資料,
Enjoy GreatSQL ??
關于 GreatSQL
GreatSQL是由萬里資料庫維護的MySQL分支,專注于提升MGR可靠性及性能,支持InnoDB并行查詢特性,是適用于金融級應用的MySQL分支版本,
相關鏈接: GreatSQL社區 Gitee GitHub Bilibili
GreatSQL社區:
歡迎來GreatSQL社區發帖提問
https://greatsql.cn/

技術交流群:
微信:掃碼添加
GreatSQL社區助手微信好友,發送驗證資訊加群,

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/519325.html
標籤:其他
