一文解析MySQL的MVCC實現原理

1. 什麼是MVCC

MVCC全稱是Multi-Version Concurrency Control(多版本並發控制),是一種並發控制的方法,通過維護一個數據的多個版本,減少讀寫操作的沖突。

如果沒有MVCC,想要實現同一條數據的並發讀寫,還要保證數據的安全性,就需要操作數據的時候加讀鎖和寫鎖,這樣就降低瞭數據庫的並發性能。

有瞭MVCC,就相當於把同一份數據生成瞭多個版本,在操作的開始各生成一個快照,讀寫操作互不影響。無需加鎖,也實現數據的安全性和事務的隔離性。

事務的四大特性中隔離性就是基於MVCC實現的。

說MVCC的實現原理之前,先說一下事務的隔離級別。

2. 事務的隔離級別

說隔離級別之前,先說一下並發事務產生的問題

臟讀: 一個事務讀到其他事務未提交的數據。

不可重復讀: 相同的查詢條件,多次查詢到的結果不一致,即讀到其他事務提交後的數據。

幻讀: 相同的查詢條件,多次查詢到的結果不一致,即讀到其他事務提交後的數據。

不可重復讀與幻讀的區別是: 不可重復讀是讀到瞭其他事務執行update、delete後的數據,而幻讀是讀到其他事務執行insert後的數據。

再說一下事務的四大隔離級別:

Read UnCommitted(讀未提交): 讀到其他事務未提交的數據,會出現臟讀、不可重復讀、幻讀。

Read Committed(讀已提交): 讀到其他事務已提交的數據,解決瞭臟讀,會出現不可重復讀、幻讀。

Repeatable Read(可重復讀): 相同的條件,多次讀取到的結果一致。解決瞭臟讀、不可重復讀,會出現幻讀。

Serializable(串行化): 所有事務串行執行,解決瞭臟讀、不可重復讀、幻讀。

隔離級別 臟讀 不可重復讀 幻讀
讀未提交
讀已提交 不會
可重復讀 不會 不會
串行化 不會 不會 不會

MVCC隻在Read CommittedRepeatable Read兩個隔離級別下起作用,因為Read UnCommitted隔離級別下,讀寫都不加鎖,Serializable隔離級別下,讀寫都加鎖,也就不需要MVCC瞭。

再談一下Undo log日志。

3. Undo Log(回滾日志)

Undo Log記錄的是邏輯日志,也就是SQL語句。

比如:當我們執行一條insert語句時,Undo Log就記錄一條相反的delete語句。

作用:

  • 回滾事務時,恢復到修改前的數據。
  • 實現 MVCC 。

事務四大特性中原子性也是基於Undo Log實現的。

下面開始談一下MVCC的實現原理。

4. MVCC的實現原理

4.1 當前讀和快照讀

先普及一下什麼是當前讀和快照讀。

當前讀: 讀取數據的最新版本,並對數據進行加鎖。

例如:insert、update、delete、select for update、 select lock in share mode。

快照讀: 讀取數據的歷史版本,不對數據加鎖。

例如:select

MVCC是基於Undo Log、隱藏字段、Read View(讀視圖)實現的。

4.2 隱藏字段

先說一下MySQL的隱藏字段,當我們創建一張表時,InnoDB引擎會增加2個隱藏字段。

DB_TRX_ID(最近一次提交事務的ID):修改表數據時,都會提交事務,每個事務都有一個唯一的ID,這個字段就記錄瞭最近一次提交事務的ID。

DB_ROLL_PTR(上個版本的地址):修改表數據時,舊版本的數據都會被記錄到Undo Log日志中,每個版本的數據都有一個版本地址,這個字段記錄的就是上個版本的地址。

4.3 版本鏈

當我們第一次往用戶表插入一條記錄時,表數據和隱藏字段的值是下面這樣的:

insert into user (name,age) values ('一燈',1);

事務ID(DB_TRX_ID)是1,上個版本地址(DB_ROLL_PTR)是null。

image-20220814230054004.png

 第二次提交事務,把用戶年齡加1。

update user set age=age+1 where id=1;

事務ID變成2,上個版本地址指向Undo Log中的記錄。

image-20220814230233034.png

第三次提交事務,再把用戶年齡加1。

update user set age=age+1 where id=1;

事務ID變成3,上個版本地址指向Undo Log中事務ID為2的記錄。

image-20220814230426413.png

這樣表記錄和Undo Log歷史數據就組成瞭一個版本鏈。

4.4 Read View(讀視圖)

在事務中,執行SQL查詢,就會生成一個讀視圖,是用來保證數據的可見性,即讀到Undo Log中哪個版本的數據。

快照讀一般是讀取的歷史版本的讀視圖,當前圖會生成一個最新版本的讀視圖。

讀視圖是基於下面幾個字段實現的:

m_ids :當前系統中活躍的事務ID集合,即未提交的事務。

min_trx_id :m_ids中最小的ID

max_trx_id :下一個要分配的事務ID

creator_trx_id: 當前事務ID

讀視圖決定當前事務能讀到哪個版本的數據,從表記錄到Undo Log歷史數據的版本鏈,依次匹配,滿足哪個版本的匹配規則,就能讀到哪個版本的數據,一旦匹配成功就不再往下匹配。

數據可見性規則:

  • DB_TRX_ID = creator_trx_id 如果這個版本數據的事務ID等於當前事務ID,表示數據記錄的最後一次操作的事務就是當前事務,當前讀視圖可以讀到這個版本的數據。
  • DB_TRX_ID < min_trx_id 如果這個版本數據的事務ID小於所有活躍事務ID,表示這個版本的數據不再被事務使用,即事務已提交,當前讀視圖可以讀到這個版本的數據。
  • DB_TRX_ID >= max_trx_id 如果這個版本數據的事務ID大於等於下一個要分配的事務ID,表示有新事務更新瞭這個版本的數據,這種情況下,當前讀視圖不可以讀到這個版本的數據。
  • min_trx_id <= DB_TRX_ID < max_trx_id 如果這個版本數據的事務ID在當前系統中活躍的事務ID集合(m_ids)裡面,表示這個版本的數據被其他事務更新過,當前讀視圖不可以讀到這個版本的數據。 如果這個版本數據的事務ID不在當前系統中活躍的事務ID集合(m_ids)裡面,表示是在其他事務提交後創建的讀視圖,當前讀視圖可以讀到這個版本的數據。

5. 不同隔離級別下可見性分析

在不同的事務隔離級別下,生成讀視圖的規則不同:

  • READ COMMITTED(讀已提交) :在事務中每一次執行快照讀時都生成一個讀視圖,每個讀視圖中四個字段的值都是不同的。
  • REPEATABLE READ(可重復讀):僅在事務中第一次執行快照讀時生成讀視圖,後續復用這個讀視圖。

5.1 READ COMMITTED(讀已提交)

設置MySQL隔離級別為讀已提交:

SET session TRANSACTION ISOLATION LEVEL READ COMMITTED;

image-20220815155838874.png

執行兩個事務,驗證一下:

image-20220815160030220.png

事務1第一次查詢時,會生成一個讀視圖,讀視圖的各個屬性如下:

屬性
m_ids 1,2
min_limit_id 1
max_limit_id 3
creator_trx_id 1

可見的版本鏈數據是:

image-20220815160452959.png

符號規則 DB_TRX_ID = creator_trx_id = 1,可以看到當前版本的數據。

事務1第二次查詢時,會生成一個新的讀視圖,讀視圖的各個屬性如下:

屬性
m_ids 1
min_limit_id 1
max_limit_id 3
creator_trx_id 1

可見的版本鏈數據是:

image-20220815174212106.png

符號規則 min_trx_id <= DB_TRX_ID < max_trx_id(1<=2<3),並且當前數據版本的事務ID不在當前系統中活躍的事務ID集合,可以看到當前版本的數據。

image-20220815192630458.png

同一個事務內,相同的查詢條件,查詢到的數據不一致,查到瞭其他事務更新過的數據,也就是出現瞭不可重復讀的情況。

再看一下,在可重復讀隔離級別下,是怎麼解決這個問題的。

5.2 REPEATABLE READ(可重復讀)

設置MySQL隔離級別為可重復讀:

SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ;

image-20220815192509582.png

執行兩個事務,驗證一下:

image-20220815160030220.png

事務1第一次查詢時,會生成一個讀視圖,讀視圖的各個屬性如下:

屬性
m_ids 1,2
min_limit_id 1
max_limit_id 3
creator_trx_id 1

可見的版本鏈數據是:

image-20220815160452959.png

符號規則 DB_TRX_ID = creator_trx_id = 1,可以看到當前版本的數據。

事務1第二次查詢時,會復用原有的讀視圖,讀視圖的各個屬性如下:

屬性
m_ids 1,2
min_limit_id 1
max_limit_id 3
creator_trx_id 1

可見的版本鏈數據是:

image-20220815174212106.png

符號規則 min_trx_id <= DB_TRX_ID < max_trx_id(1<=2<3),並且當前數據版本的事務ID在當前系統中活躍的事務ID集合,所以是不可以看到當前版本的數據。

image-20220815193850487.png

由此得知,可重復讀隔離級別下,相同的查詢條件,兩次查詢到的結果相同,也就是解決瞭可重復讀的問題,是通過復用原有的讀視圖的方式解決的。

到此這篇關於一問解析MySQL的MVCC實現原理的文章就介紹到這瞭,更多相關MySQL MVCC實現內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: