1. 程式人生 > >對Innodb中MVCC的理解

對Innodb中MVCC的理解

一、什麼是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無法完成。

二、MVCC實現的核心知識點

1、事務版本號   每次事務開啟前都會從資料庫獲得一個自增長的事務ID,可以從事務ID判斷事務的執行先後順序。   可以通過這樣的命令來檢視:select TRX_ID from INFORMATION_SCHEMA.INNODB_TRX;   2、隱藏欄位(Innodb 為每行額外添加了3個欄位,具體請參考官方文件): DB_TRX_ID:大小為6個位元組。指插入或更新該行的最後一個事務的事務識別符號,也就是事務ID。 此外,刪除在內部被視為更新,在該更新中,該行中的特殊位被設定為將其標記為已刪除。 DB_ROLL_PTR:大小為7個位元組。表示指向該行回滾段的指標。 回滾指標指向寫入回滾段的撤消日誌記錄。 如果行已更新,則撤消日誌記錄將包含在更新行之前重建行內容所必需的資訊。 DB_ROW_ID:大小為6個位元組。包含一個行ID,該行ID隨著插入新行而單調增加。 如果InnoDB自動生成聚集索引,則該索引包含行ID值。 否則,DB_ROW_ID列不會出現在任何索引中。
3、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/ 4、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陣列的上下界(注意:從單詞上來區分的話很容易弄反)。 其他事務對當前事務的可見性判斷如下:   

三、案例分析

下面通過案例來分析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在活躍事務列表中不可見 而103、104、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的都不可見    
   

四、總結

  1、MVCC主要靠Read view來實現一致性讀,也就是快照讀;底層是主要基於其中兩個隱藏欄位來實現(DB_TRX_ID、DB_ROLL_PTR)。這樣可以使不同事務的讀-寫、寫-讀操作併發執行,從而提升系統性能。   2、Read view其中幾個重要組成屬性(trx_ids、low_limit_id、up_limit_id、creator_trx_id),一旦一個Read View被建立,這三個引數將不再發生變化;   3、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即可。

五、參考文獻

http://mysql.taobao.org/monthly/2018/03/01/ https://github.com/twitter-forks/mysql/blob/master/storage/innobase/include/read0read.h#L124 https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html