1. 什么是MVCC
MVCC全稱是Multi-Version Concurrency Control(多版本并發控制),是一種并發控制的方法,通過維護一個資料的多個版本,減少讀寫操作的沖突,
如果沒有MVCC,想要實作同一條資料的并發讀寫,還要保證資料的安全性,就需要操作資料的時候加讀鎖和寫鎖,這樣就降低了資料庫的并發性能,
有了MVCC,就相當于把同一份資料生成了多個版本,在操作的開始各生成一個快照,讀寫操作互不影響,無需加鎖,也實作資料的安全性和事務的隔離性,
事務的四大特性中隔離性就是基于MVCC實作的,
說MVCC的實作原理之前,先說一下事務的隔離級別,
2. 事務的隔離級別
說隔離級別之前,先說一下并發事務產生的問題:
臟讀: 一個事務讀到其他事務未提交的資料,
不可重復讀: 相同的查詢條件,多次查詢到的結果不一致,即讀到其他事務提交后的資料,
幻讀: 相同的查詢條件,多次查詢到的結果不一致,即讀到其他事務提交后的資料,
不可重復讀與幻讀的區別是: 不可重復讀是讀到了其他事務執行update、delete后的資料,而幻讀是讀到其他事務執行insert后的資料,
再說一下事務的四大隔離級別:
Read UnCommitted(讀未提交): 讀到其他事務未提交的資料,會出現臟讀、不可重復讀、幻讀,
Read Committed(讀已提交): 讀到其他事務已提交的資料,解決了臟讀,會出現不可重復讀、幻讀,
Repeatable Read(可重復讀): 相同的條件,多次讀取到的結果一致,解決了臟讀、不可重復讀,會出現幻讀,
Serializable(串行化): 所有事務串行執行,解決了臟讀、不可重復讀、幻讀,
| 隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
|---|---|---|---|
| 讀未提交 | 會 | 會 | 會 |
| 讀已提交 | 不會 | 會 | 會 |
| 可重復讀 | 不會 | 不會 | 會 |
| 串行化 | 不會 | 不會 | 不會 |
MVCC只在Read Committed和Repeatable 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,

第二次提交事務,把用戶年齡加1,
update user set age=age+1 where id=1;
事務ID變成2,上個版本地址指向Undo Log中的記錄,

第三次提交事務,再把用戶年齡加1,
update user set age=age+1 where id=1;
事務ID變成3,上個版本地址指向Undo Log中事務ID為2的記錄,

這樣表記錄和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;

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

事務1第一次查詢時,會生成一個讀視圖,讀視圖的各個屬性如下:
| 屬性 | 值 |
|---|---|
| m_ids | 1,2 |
| min_limit_id | 1 |
| max_limit_id | 3 |
| creator_trx_id | 1 |
可見的版本鏈資料是:

符號規則 DB_TRX_ID = creator_trx_id = 1,可以看到當前版本的資料,
事務1第二次查詢時,會生成一個新的讀視圖,讀視圖的各個屬性如下:
| 屬性 | 值 |
|---|---|
| m_ids | 1 |
| min_limit_id | 1 |
| max_limit_id | 3 |
| creator_trx_id | 1 |
可見的版本鏈資料是:

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

同一個事務內,相同的查詢條件,查詢到的資料不一致,查到了其他事務更新過的資料,也就是出現了不可重復讀的情況,
再看一下,在可重復讀隔離級別下,是怎么解決這個問題的,
5.2 REPEATABLE READ(可重復讀)
設定MySQL隔離級別為可重復讀:
SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ;

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

事務1第一次查詢時,會生成一個讀視圖,讀視圖的各個屬性如下:
| 屬性 | 值 |
|---|---|
| m_ids | 1,2 |
| min_limit_id | 1 |
| max_limit_id | 3 |
| creator_trx_id | 1 |
可見的版本鏈資料是:

符號規則 DB_TRX_ID = creator_trx_id = 1,可以看到當前版本的資料,
事務1第二次查詢時,會復用原有的讀視圖,讀視圖的各個屬性如下:
| 屬性 | 值 |
|---|---|
| m_ids | 1,2 |
| min_limit_id | 1 |
| max_limit_id | 3 |
| creator_trx_id | 1 |
可見的版本鏈資料是:

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

由此得知,可重復讀隔離級別下,相同的查詢條件,兩次查詢到的結果相同,也就是解決了可重復讀的問題,是通過復用原有的讀視圖的方式解決的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/505358.html
標籤:MySQL
上一篇:在社交應用中獲取用戶資料
下一篇:MySQL第四天
