GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 前情提要 當前讀 快照讀 什麼是MVCC 三個隱藏欄位 Undo Log回滾日誌 MVCC版本鏈 ReadView讀視圖 不同隔離級別下MVCC分析 R ...
- 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