對Innodb中MVCC的理解

1、什麼是MVCC

  MVCC (Multiversion Concurrency Control) 中文全程叫 多版本併發控制,是現代數據庫(如MySql)引擎實現中經常使用的 處理讀寫衝突的手段,目的在於 提升數據庫高併發場景下的吞吐性能
  MySQL的InnoDB存儲引擎默認事務隔離級別是RR(可重複讀),是經過 "行級鎖+MVCC"一塊兒實現的,正常讀的時候不加鎖,寫的時候加鎖。而  MCVV 的實現依賴:隱藏字段、Read View、Undo log
  另外MVCC只在 Read Committed 和 Repeatable Read兩個隔離級別下工做,其餘兩個隔離級別和MVCC不兼容:
  • Read Uncommitted老是讀取最新的記錄行,不須要MVCC的支持;
  • Serializable 則會對全部讀取的記錄行都加鎖,單靠MVCC沒法完成。

2、MVCC實現的核心知識點

一、事務版本號
  每次事務開啓前都會從數據庫得到一個自增加的事務ID,能夠從事務ID判斷事務的執行前後順序。
  能夠經過這樣的命令來查看:select TRX_ID from INFORMATION_SCHEMA.INNODB_TRX;
 
二、隱藏字段(Innodb 爲每行額外添加了3個字段,具體請參考 官方文檔):
DB_TRX_ID:大小爲6個字節。指插入或更新該行的最後一個事務的事務標識符,也就是事務ID。 此外,刪除在內部被視爲更新,在該更新中,該行中的特殊位被設置爲將其標記爲已刪除。
DB_ROLL_PTR:大小爲7個字節。表示指向該行回滾段的指針。 回滾指針指向寫入回滾段的撤消日誌記錄。 若是行已更新,則撤消日誌記錄將包含在更新行以前重建行內容所必需的信息。
DB_ROW_ID:大小爲6個字節。包含一個行ID,該行ID隨着插入新行而單調增長。 若是InnoDB自動生成彙集索引,則該索引包含行ID值。 不然,DB_ROW_ID列不會出如今任何索引中。
三、Undo log
Undo log是InnoDB MVCC事務特性的重要組成部分。Undo log 主要用於記錄數據被修改以前的日誌,在表信息修改以前先會把數據拷貝到undo log 裏,當事務進行回滾時能夠經過undo log 裏的日誌進行數據還原。具體就不詳細介紹了,請看考這兩篇文檔: https://dev.mysql.com/doc/refman/8.0/en/innodb-undo-logs.html http://mysql.taobao.org/monthly/2015/04/01/
四、read view
「InnoDB支持MVCC多版本,其中RC(Read Committed)和RR(Repeatable Read)隔離級別是利用consistent read view(一致讀視圖)方式支持的。所謂consistent read view就是在某一時刻給事務系統trx_sys打snapshot(快照),把當時trx_sys狀態(包括活躍讀寫事務數組)記下來,以後的全部讀操做根據其事務ID(即trx_id)與snapshot中的trx_sys的狀態做比較,以此判斷read view對於事務的可見性。
RR隔離級別(除了Gap鎖以外)和RC隔離級別的差異是建立snapshot時機不一樣。 RR隔離級別是在事務開始時刻,確切地說是第一個讀操做建立read view的;RC隔離級別是在語句開始時刻建立read view的(詳見 官方文檔)。」
 
Read view中保存的trx_sys狀態主要包括(如下字段解釋來源於 源碼):
trx_ids: 爲活躍事務id列表,即Read View初始化時當前未提交的事務列表。因此當進行RR讀的時候,trx_ids中的事務對於本事務是不可見的(除了自身事務,自身事務對於表的修改對於本身固然是可見的)。
low_limit_id: 當前最大的事務id + 1,事務id >= low_limit_id,對於當前Read View都是不可見的。理解起來就是在建立Read View視圖的時候,以後建立的事務對於該事務確定是不可見的。
up_limit_id: 當前已經提交的事務id + 1,事務id < up_limit_id ,對於當前Read View都是可見的。 理解起來就是在建立Read View視圖的時候,以前已經提交的事務對於該事務確定是可見的。
creator_trx_id: 建立當前read view的事務版本號;
一旦一個Read View被建立,這三個參數將再也不發生變化,理解這點很重要,其中low_limit_id 和 up_limit_id分別是 trx_Ids數組的上下界(注意:從單詞上來區分的話很容易弄反)。
其餘事務對當前事務的可見性判斷以下:
  

3、案例分析

下面經過案例來分析MVCC怎麼實現一致性讀取的。前期數據準備:
  • 使用默認隔離級別RR;
  • 建立一個表: create table test(id int AUTO_INCREMENT, score int, primary key(id)) AUTO_INCREMENT = 0;
  • 假設當前事務id已經自增加到100;
步驟
事務1
事務2
事務3
1
begin;
   
2
 
begin;
 
3
insert into test(score) select 101;
此時事務ID爲101
   
4
 
insert into test(score) select 102;
此時事務ID爲102
 
5
select * from test;
+----+-------+
| id | score |
+----+-------+
| 1 | 101 |
+----+-------+
此時就會建立read view
up_limit_id = 101
low_limit_id = 103
trx_ids爲(101,102)
而101自身可見,102在活躍事務列表中不可見
   
6
   
insert into test(score) select 103;
此時事務ID爲103
7
   
insert into test(score) select 104;
此時事務ID爲104
8
   
nsert into test(score) select 105;
此時事務ID爲105
9
   
select * from test;
+----+-------+
| id | score |
+----+-------+
| 3 | 103 |
| 4 | 104 |
| 5 | 105 |
+----+-------+
此時的up_limit_id=101,
low_limit_id=106
trx_ids爲(101, 102),
而101和102在trx_ds列表中不可見 
10
 
select * from test;
+----+-------+
| id | score |
+----+-------+
| 2 | 102 |
| 3 | 103 |
| 4 | 104 |
| 5 | 105 |
+----+-------+
此時就會建立read view:
up_limit_id=101,
low_limit_id=106,
trx_ids爲(101, 102),
102自身可見,101在活躍事務列表中不可見
而10三、10四、105不在trx_ids列表中全部可見
 
11
select * from test;
+----+-------+
| id | score |
+----+-------+
| 1 | 101 |
| 3 | 103 |
| 4 | 104 |
| 5 | 105 |
+----+-------+
因爲事務內read view不變
(與RC的區別就在這),
此時的up_limit_id=101,low_limit_id=103,
trx_ids爲(101, 102),
101自身可見,102在活躍事務列表中不可見
而>=103的都不可見
   
 
 

4、總結

  一、MVCC主要靠Read view來實現一致性讀,也就是快照讀;底層是主要基於其中兩個隱藏字段來實現(DB_TRX_ID、DB_ROLL_PTR)。這樣可使不一樣事務的讀-寫、寫-讀操做併發執行,從而提高系統性能。
  二、Read view其中幾個重要組成屬性(trx_ids、low_limit_id、up_limit_id、creator_trx_id),一旦一個Read View被建立,這三個參數將再也不發生變化;
  三、MVCC只在 RC 和 RR兩個隔離級別下工做, 它們的不一樣之處在於:
    RR:read view是在first touch read時建立的,也就是執行事務中的第一條SELECT語句的瞬間,後續全部的SELECT都是複用這個read view,因此能保證每次讀取的一致性(可重複讀的語義)
    RC:每次讀取,都會建立一個新的read view。這樣就能讀取到其餘事務已經COMMIT的內容。
  因此對於InnoDB來講,RR雖然比RC隔離級別高,可是開銷反而相對少。
  補充:RU的實現就簡單多了,不使用read view,也不須要管什麼DB_TRX_ID和DB_ROLL_PTR,直接讀取最新的record便可。

5、參考文獻

相關文章
相關標籤/搜索