MVCC--多版本並發控制機制


  關鍵詞:MVCC、解決幻讀、read_view、undo log、快照讀、當前讀

  前言:
      以下的分析均在mysql的InnoDB引擎下。
      假設此時事務A與事務B同時執行。

1、定義:

  MVCC(Multi-Version Concurrency Control,多版本並發控制)一種並發控制機制,在數據庫中用來控制並發執行的事務,控制事務隔離進行。

2、核心思想:

  MVCC是通過保存數據在某個時間點的快照來進行控制的。使用MVCC就是允許同一個數據記錄擁有多個不同的版本。然后在查詢時通過添加相對應的約束條件,就可以獲取用戶想要的對應版本的數據。

3、基本數據結構:

(1)redo log:

  重做日志記錄。存儲事務操作的最新數據記錄,方便日后使用。

(2)undo log:

  撤回日志記錄,也稱版本鏈。當前事務未提交之前,undo log保存了當前事務的正在操作的數據記錄的所有版本的信息,undo log中的數據可作為數據舊版本快照供其他並發事務進行快照讀。每次有其它事務提交對當前數據行的修改,都是添加到undo log中。undo log是由每個數據行的多個不同的版本鏈接在一起構成的一個記錄“鏈表”。如下圖:

(3)read_view(快照):

①read_view的簡單理解:

  會對數據在每個時刻的狀態拍成照片記錄下來。那么之后獲取某時刻的數據時就還是原來的照片上的數據,是不會變的。其實也可以簡單理解為是一個版本鏈的集合,只不過在這里的版本鏈是經過篩選的。

②read_view的基本結構:

read_view->creator_trx_id = 當前事務id; # 當前的事務id
read_view->up_limit_id = 12654;        # 當前活躍事務的最小id
read_view->low_limit_id = 12659;       # 當前活躍事務的最小id
read_view->trx_ids = [12654, 12659];   # 當前活躍的事務的id列表,又稱活躍事務鏈表。表示在記錄當前快照時的所有活躍的、未提交的事務
read_view->m_trx_ids = 2;              # 當前活躍的事務id列表長度

注意:

  • read_view中包含了活躍事務鏈表,這個鏈表表示此時還在活躍的事務,指的是那些在當前快照中還未提交的事務。(注意:新建事務(當前事務)與正在內存中commit 的事務不在活躍事務鏈表)。
  • read_view中不會顯示所有的數據行,只會顯示“可見”的記錄。篩選方式如下所述。

③read_view的記錄篩選方式:

前提:DATA_TRX_ID 表示每個數據行的最新的事務ID;up_limit_id表示當前快照中的最先開始的事務;low_limit_id表示當前快照中的最慢開始的事務,即最后一個事務。
  • 如果記錄的DATA_TRX_ID < up_limit_id:在創建read_view時,修改該記錄的事務已提交,該記錄可被快照中的事務讀取到(即可見)。

  • 如果DATA_TRX_ID >= low_limit_id:表示該記錄是在當前read_view創建之后被其它事務修改的,該記錄在當前快照中肯定不可見。此時需要從DB_ROLL_PTR指針所指向的回滾段中取出最新的undo-log的版本號, 然后用它繼續重新開始整套比較算法。

  • 如果up_limit_id <= DATA_TRX_ID < low_limit_i:

    • 需要在活躍事務鏈表中查找是否存在ID為DATA_TRX_ID的值的事務。

    • 如果存在,那么因為在活躍事務鏈表中的事務是未提交的,所以該記錄是不可見的。此時需要從DB_ROLL_PTR指針所指向的回滾段中取出最新的undo-log的版本號, 然后用它繼續重新開始整套比較算法。(詳細分析為什么“不可見”:因為DATA_TRX_ID只有在事務提交之后才會更新,而此時因為事務還存在於活躍事務鏈表中,所以說明事務是還沒有commit,所以此時不可能存在對應的數據行,只有在當前事務提交之后才會有對應的數據行。)

    • 如果不存在,所以是可見的。(分析:按照上一點的對“不可見”原因的分析,可明白只能是當前本事務更新了這條記錄,因為在當前read view中,只能是當前事務和正在內存中commit的事務不在事務活躍鏈表中,對於“正在內存中commit的事務”,因為它還沒有commit,所以肯定是不可能讀取到它的即將要commit的數據的,而所以只能是當前事務對這個數據行做了修改了,雖然未提交,但是因為是在當前事務中,所以肯定是可以讀取到更新的數據的。

④read_view的更新方式:

注意:僅分析RC級別和RR級別,因為MVCC不適用於其它兩個隔離級別。

a、對於Read Committed級別的:

  • 基本描述:每次執行select都會創建新的read_view,更新舊read_view,保證能讀取到其他事務已經COMMIT的內容(讀提交的語義);
  • 詳細分析:假設當前有事務A和事務A+1並發進行。在當前級別下,事務A每次select的時候會創建新的read_view,此時可以簡單理解為事務A會提交,也就是讓事務A執行完畢,然后創建一個新的事務比如是事務A+2。這樣子的話,因為事務A+2的事務ID肯定是比事務A+1的ID大,所以就能夠讀取到事務A+1的更新了。那么便可以讀取到在創建這個新的read_view之前事務A+1所提交的所有信息。這是RC級別下能讀取到其他事務已經COMMIT的內容的原因所在。

b、對於Repeatable Read級別的:

  • 第一次select時更新這個read_view,以后不會再更新,后續所有的select都是復用這個read_view。所以能保證每次讀取的一致性,即都是讀取第一次讀取到的內容(可重復讀的語義)。

注意:通過對read view的更新方式的分析可以得出:對於InnoDB下的MVCC來說,RR雖然比RC隔離級別高,但是開銷反而相對少(因為不用頻繁更新read_view)

read_view的詳細分析:https://www.iteye.com/blog/mahl1990-2347029

4、MVCC在mysql的具體實現:

(1)基本數據結構的定義:

在mysql中,在實現MVCC時,會為每一個表添加如下幾個隱藏的字段:
  • 6字節的DATA_TRX_ID:標記了最新更新這條行記錄的transaction id,每處理一個事務,其值自動設置為當前事務ID(DATA_TRX_ID只有在事務提交之后才會更新);
  • 7字節的DATA_ROLL_PTR:一個rollback指針,指向當前這一行數據的上一個版本,找之前版本的數據就是通過這個指針,通過這個指針將數據的多個版本連接在一起構成一個undo log版本鏈;
  • 6字節的DB_ROW_ID:隱含的自增ID,如果數據表沒有主鍵,InnoDB會自動以DB_ROW_ID產生一個聚簇索引。這是一個用來唯一標識每一行的字段;
  • DELETE BIT位:用於標識當前記錄是否被刪除,這里的不是真正的刪除數據,而是標志出來的刪除。真正意義的刪除是在commit的時候。

 

MVCC在二級索引結構下的分析:https://www.cnblogs.com/stevenczp/p/8018986.html

(2)增刪改查:

①增加:INSERT

  • 設置新記錄的DATA_TRX_ID為當前事務ID,其他的采用默認的。

②刪除:DELETE

  • 修改DATA_TRX_ID的值為當前的執行刪除操作的事務的ID,然后設置DELETE BIT為True,表示被刪除

③修改:UPDATE <==> INSERT + DELETE

  • 用X鎖鎖定該行(因為是寫操作);
  • 記錄redo log:將更新之后的數據記錄到redo log中,以便日后使用;
  • 記錄undo log:將更新之后的數據記錄到undo log中,設置當前數據行的DATA_TRX_ID為當前事務ID,回滾指針DATA_ROLL_PTR指向undo log中的當前數據行更新之前的數據行,同時設置更新之前的數據行的DATA_TRX_ID為當前事務ID,並且設置DELETE BIT為True,表示被刪除。

④查找:SELECT

  • 如果當前數據行的DELETE BIT為False,只查找版本早於當前事務版本的數據行(也就是數據行的DATA_TRX_ID必須小於等於當前事務的ID),這確保當前事務讀取的行都是事務之前已經存在的,或者是由當前事務創建或修改的行;
  • 如果當前數據行的DELETE BIT為True,表示被刪除,那么只能返回DATA_TRX_ID的值大於當前事務的行。獲取在當前事務開始之前,還沒有被刪除的行。
注意:
  a、此時就是要去查找read_view,判斷其中是否有需要的記錄;
  b、就算在當前事務提交的時候,也不會讀取到DATA_TRX_ID大於當前事務ID的數據記錄(而默認情況下,RR隔離級別下,當前事務一commit,就能夠讀取到其他事務的commit)。這也是MVCC能夠解決幻讀的原因

5、使用MVCC核心優勢:

(1)在mysql中,使用MVCC本質上是為了在進行讀操作的時候代替加鎖,減少加鎖帶來的負擔。
(2)在mysql的InnoDB引擎,並且是在RR隔離級別下,通過使用MVCC和gap鎖來解決幻讀問題(詳細解決方式參見下方的第7點)。

6、MVCC與四大隔離級別的關系的分析:

分析了在MVCC的控制之下,如何實現四大隔離級別。

(1)Read Uncimmitted級別

  由於存在臟讀,即能讀到未提交事務的數據行,所以不適用MVCC。原因是MVCC的DATA_TRX_ID只有在事務提交之后才會更新,而在Read uncimmitted級別下,由於是讀取未提交的,所以說MVCC在這個級別下是不適用的。

(2)Read Committed級別

查找操作:

  分析:假設當前有事務A、事務A+1、數據B(DATA_TRX_ID為A-1)。

  • 事務A進行查找,此時找出事務ID小於它本身的,所以此時數據B可以被找到;
  • 如果在事務A還沒有執行完畢的時候,事務A+1對數據B進行了更新操作,那么此時數據B的undo log則被更新為“數據B(DATA_TRX_ID為A+1)-> 數據B(DATA_TRX_ID為A-1)”;
  • 此時如果事務A再次進行查找操作,會更新read_view。更新舊的read_view,並且開啟新的事務A+2。那么根據MVCC的規定,就能夠找到數據B(DATA_TRX_ID為A+1),可以找到更新之后的。這樣子的話就等價於能夠讀取到別的事務commit的最新的數據記錄。這就符合RC級別的語義

(3)Repeatable Read級別:

查找操作:

  分析:假設當前有:事務A、事務A+1,數據B(DATA_TRX_ID為A-1)。
  • 事務A進行查找,此時找出事務ID小於它本身的,所以此時數據B可以被找到;
  • 如果在事務A還沒有執行完畢的時候,事務A+1對數據B進行了更新操作,那么此時數據B的undo log則被更新為“數據B(DATA_TRX_ID為A+1)-> 數據B(DATA_TRX_ID為A-1)”;
  • 此時如果事務A再次進行查找操作,那么根據MVCC的規定,還是只能找到數據B(DATA_TRX_ID為A-1)(因為B(DATA_TRX_ID為A+1)的事務ID比當前事務A的事務ID大,所以不會被找到),不會找到更新之后的。這樣子的話就等價於只能夠讀取到事務A開始時讀取到的數據記錄。這就符合RR級別的語義

(4)Serialization級別:

  串行化由於是會對所涉及到的表加鎖,並非行鎖,自然也就不存在行的版本控制問題

總結:通過上面的分析可得:MVCC只適用於MySQL隔離級別中的讀已提交(Read committed)和可重復讀(Repeatable Read)

7、MVCC、gap鎖解決幻讀問題的分析:

前提:InnoDB引擎、RR隔離級別(gap鎖只存在於這個級別下)

(1)首先了解數據記錄的讀取方式:快照讀和當前讀

①快照讀:

  讀快照,可以讀取數據的所有版本信息,包括舊版本的信息。其實就是讀取MVCC中的read_view,同時結合MVCC進行相對應的控制;

select * from table where ?;

②當前讀:

  讀當前,讀取當前數據的最新版本。而且讀取到這個數據之后會對這個數據加鎖,防止別的事務更改。

(分析:在進行寫操作的時候就需要進行“當前讀”,讀取數據記錄的最新版本)

select * from table where ? lock in share mode;  # 讀鎖
select * from table where ? for update;          # 寫鎖
insert into table values (…); 
update table set ? where ?; 
delete from table where ?;

詳見:https://www.jianshu.com/p/27352449bcc0

③RC和RR隔離級別下的快照讀和當前讀:

  • RC隔離級別下,快照讀和當前讀結果一樣,都是讀取已提交的最新;
  • RR隔離級別下,當前讀結果是其他事務已經提交的最新結果,快照讀是讀當前事務之前讀到的結果。RR下創建快照讀的時機決定了讀到的版本。

(2)解決幻讀問題:

①對於快照讀:通過MVCC來進行控制的,不用加鎖。按照MVCC中規定的“語法”進行增刪改查等操作,以避免幻讀。(MVCC的具體內容參見上方第1點到第4點的分析)

②對於當前讀:通過next-key鎖(行鎖+gap鎖)來解決問題的。(next-key鎖的分析:mysql中的鎖

(3)特殊語句分析:

“MVCC不能根本上解決幻讀的情況?”

分析:這句話的含義是指對於快照讀,那么是可以通過MVCC來解決的;但是對於當前讀,則必須通過next-key鎖(行鎖+gap鎖)來解決。

 

 

 

參考資料: 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM