MVCC多版本併發控制
持續創作,加速成長!這是我參與「掘金日新計劃 · 10 月更文挑戰」的第5天,點選檢視活動詳情
MVCC定義
1、MVCC簡介
MVCC,全稱Multi-Version Concurrency Control,即多版本井發控制,MVCC是一種併發控制的方法,一般在資料庫管理系統中,實現對資料庫的併發訪問,在程式語言中實現事務記憶體。 MVCC在MysQL InnoDB中的實現主要是為了提高資料庫併發效能,用更好的方式去處理讀寫衝突。做到即使有讀寫衝突時,也能做到不加鎖,非阻塞併發讀。
2、當前讀
像select lock in share mode(共享鎖),select forupdate;update,insert,delete(排他鎖)這些操作都是一種當前讀,為什麼叫當前讀?就是它讀取的是記錄的最新版本,讀取時還要保證其他併發事務不能修改當前記錄,會對讀取的記錄進行加鎖。
3、快照讀
像不加鎖的select操作就是快照讀,即不加鎖的非阻塞讀;快照讀的前提是隔離級別不是序列圾別,序列圾別下的快照讀會退化成當前讀;之所以出現快照讀的情況,是基於提高併發效能的考慮,快照讀的實現是基於多版本併發控制,即MVCC,可以認為MVCC是行鎖的一個變種,但它在很多情況下,避免了加鎖操作,降低了開銷;既然是基於多版本,即快照讀可能讀到的並不一定是資料的最新版本,而有可能是之前的歷史版本。
4、當前讀、快照讀、MVCC關係
MVCC多版本併發控制指的是維持一個數據的多個版本,使得讀寫操作沒有衝突,快照讀是MySQL為實現MVCC的一個非阻塞讀功能。 MVCC模組在MySQL中的具體實現是由 三個隱式欄位、undo日誌、read view三個元件來實現的。
5、MVCC解決的問題
前提資料庫併發場景有三種,分別為∶ 1)、讀讀∶ 不存在任何問題,也不需要併發控制 2)、讀寫∶有執行緒安全問題,可能會造成事務隔離性問題,可能遇到髒讀、幻讀、不可重複讀 3)、寫寫∶ 有執行緒安全問題,可能存在更新丟失問題 MVCC是一種用來解決讀寫衝突的無鎖併發控制,也就是為事務分配單項增長的時間戳,為每個修改儲存一個版本,版本與事務時間戳關聯,讀操作只讀該事務開始前的資料庫的快照。 因此,MVCC可以為資料庫解決以下問題∶ 1)、在併發讀寫資料庫時,可以做到在讀操作時不用阻塞寫操作,寫操作也不用阻塞讀操作,提高了資料庫併發讀寫的效能。 2)、解決髒讀、幻讀、不可重複讀等事務隔離問題,但是不能解決更新丟失問題。
MVCC實現原理
mvcc的實現原理主要依賴於記錄中的三個隱藏欄位,undo log,read view來實現的。
1、隱藏欄位:每行記錄除了我們自定義的欄位外,還有資料庫隱式定義的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等欄位
1),DB_TRX_ID:6位元組,最近修改事務id,記錄建立這條記錄或者最後一次修改該記錄的事務id。
2),DB_ROLl_PTR:7位元組,回滾指標,指向這條記錄的上一個版本,用於配合undolog,指向上一個舊版本 。
3),DB_ROWjD:6位元組,隱藏的主鍵,如果資料表沒有主鍵,那麼innodb會自動生成一個6位元組的row_id。
隱藏欄位的記錄如同所示:
在上圖中,DB_ROW_ID是資料庫預設為該行記錄生成的唯一隱式主鍵,DB_TRX_ID是當前操作該記錄的事務ID,DB_ROLL_PTR是一個回滾指標,用於配合undo日誌,指向上一個舊版本。 2、undolog:undolog被稱之為回滾日誌,表示在進行insert,delete,update操作的時候產生的,方便回滾的日誌。
當進行insert操作的時候,產生的undolog只在事務回滾的時候需要,並且在事務提交之後可以被立刻丟棄; 當進行update和delete操作的時候,產生的undolog不僅僅在事務回滾的時候需要,在快照讀的時候也需要,所以不能隨便刪除,只有在快照讀或事務回滾不涉及該日誌時,對應的日誌才會被purge執行緒統一清除(當資料發生更新和刪除操作的時候都只是設定一下老記錄的deleted bit,並不是真正的將過時的記錄刪除,因為為了節省磁碟空間,innodb有專門的purge執行緒來清除deleted_bit為true的記錄,如果某個記錄的deletedid為true,並且DB_TRXID相對於purge執行緒的read view可見,那麼這條記錄一定時可以被清除的)。
下面我們來看-下undolog生成的記錄鏈: 1)、假設有一個事務編號為1的事務向表中插入一條記錄,那麼此時行資料的狀態為;
2)、假設有第二個事務編號為2,對該記錄的name做出修改,改為masi在事務2修改該行記錄資料時,資料庫會對該行加排他鎖。 然後把該行資料拷貝到undolog中,作為 舊記錄,即在undolog中有當前行的拷貝副本。 拷貝完畢後,修改該行name為masi,並且修改隱藏欄位的事務id為當前事務2的d,回滾指標指向拷貝到undolog的副本記錄地址,事務提交後,釋放鎖。
3)、假設有第三個事務編號為3對該記錄的age做了修改,改為32在事務3修改該行資料的時,資料庫會對該行加排他鎖。 然後把該行資料拷貝到undolog中,作為舊紀錄,發現該行記錄已經有undolog了,那麼最新的舊資料作為連結串列的表頭,插在該行記錄的undolog最前面。 修改該行age為32歲,並且修改隱藏欄位的事務id為3,回滾指標指向剛剛拷貝的undolog的副本記錄地址,事務提交,釋放鎖。
從上述的一系列圖中,大家可以發現,不同事務或者相同事務的對同一記錄的修改,會導致該記錄的undolog生成一條記錄版本線性表, 即連結串列,undolog的鏈首就是最新的舊記錄,鏈尾就是最早的舊記錄。
3、Read View 上面的流程如果看明白了,那麼大家需要再深入理解下read view的概念了: Read View是事務進行快照讀操作的時候生產的讀檢視,在該事務執行快照讀的那一刻,會生成一個數據系統當前的快照,記錄並維護系統當前活躍事務的id,事務的id值是遞增的。 其實Read View的最大作用是用來做可見性判斷的,也就是說當某個事務在執行快照讀的時候,對該記錄建立一個Read View的檢視,把它當作條件去判斷當前事務能夠看到哪個版本的資料,有可能讀取到的是最新的資料,也有可能讀取的是當前行記錄的undolog中某個版本的資料。 Read View遵循的可見性演算法主要是將要被修改的資料的最新記錄中的DB_TRX_ID(當前事務id)取出來,與系統當前其他活躍事務的id去對比,如果DB_TRX_ID跟Read Vew的屬性做了比較,不符合可見性。那麼就通過DB_ROLL_PTR回滾指標去取出undolog中的 DB_TRX_ID做比較,即遍歷連結串列中的DB_TRX_ID, 直到找到滿足條件的DB_TRX_ID。這個DB_TR_ID所在的舊記錄就是當前事務能看到的新老版本資料
Read View的可見性規則如下所示。首先要知道Read View中的三個全域性屬性∶ - trx_list∶ 一個數值列表,用來維護Read View生成時刻系統正活躍的事務ID - up_limt_id∶ 記錄trx_list列表中事務ID最小的ID - low_limit_id∶ Read View生成時刻系統尚未分配的下一個事務ID。
具體的比較規則如下∶
1)、首先比較DB_TRX_ID<up_limit_id,如果小於,則當前事務能看到DB_TRX_ID所在的記錄,如果大於等於進入下一個判斷。
2)、接下來判斷DB_TRX_ID>= low_limit_id,如果大於等於 則代表DB_TRX_ID所在的記錄在Read View生成後才出現的,那麼對於當前事務肯定不可見,如果小於,則進入下一步判斷。
3)、判斷DB_TRX_ID是否在活躍事務中,如果在,則代表在Read View生成時刻,這個事務還是活躍狀態,還沒有commit,修改的資料,當前事務也是看不到,如果不在,則說明這個事務在Read View生成之前就已經開始commit,那麼修改的結果是能夠看見的。
MVCC的整體處理流程
1、假設有四個事務同時在執行,如下圖所示∶
從上述表格中,我們可以看到,當事務2對某行資料執行了快照讀,資料庫為該行資料生成一個Read View檢視,可以看到事務1和事務3還在活躍狀態,事務4在事務2快照讀的前一刻提交了更新,所以,在Read Vew中記錄了系統當前活躍事務1,2,3,維護在一個列表中;同時可以看到up_limit_id的值1,而low _limit_id為5。如下圖所示:
在上述的例子中,只有事務4修改過該行記錄,並在事務2進行快照讀前,就提交了事務,所以該行當前資料的undolog如下所示∶
當事務2在快照讀該行記錄的是,會拿著該行記錄的DB_TRXID去跟up_limit_d,lower_llimit_id和活躍事務列表進行比較,判讀事務2能看到該行記錄的版本是哪個。
2、具體流程如下∶
先拿該行記錄的事務ID(4)去跟Read Vew中的up_limt_id相比較,判斷是否小於,通過對比發現不小於,所以不符合條件;繼續判斷4是否大於等於low_Imit id,通過比較發現也不大於,所以不符合條件,判斷事務4是否處理trx list列表中,發現不再次列表中,那麼符合可見性條件,所以事務4修改後提交的最新結果對事務2的快照是可見的,因此,事務2讀取到的最新資料記錄是事務 4所提交的版本,而事務4提交的版本也是全域性角度的最新版本。如下圖所示∶
當上述的內容都看明白了的話,那麼大家就應該能夠搞清楚這幾個核心概念之間的關係了,下面我們講一個不同的隔離級別下的快照的不同。
RC、RR級別下的InnoDB快照讀有什麼不同:
1、首先mysql四種隔離級別:
1)未提交讀(READ UNCOMMITED)髒讀
2) 已提交讀 (READ COMMITED)簡稱(RC) 不可重複讀
3)可重複讀(REPEATABLE READ)簡稱(RR )
4)可序列化(SERIALIZABLE)
2、因為Read View生成時機的不同,從而造成RC、RR級別下快照讀的結果的不同:
1)、在RR級別下的某個事務的對某條記錄的第一次快照讀會建立一個快照即Read View將當前系統活躍的其他事務記錄起來,此後呼叫快照讀的時候,還是使用的是同一個Read View, 所以只要當前事務在其他事務提交更新之前使用過快照讀,那麼之後的快照讀使用都是同一個Read View,所以對之後的修改不可見。 2)、在RR級別下,快照讀生成ReadView時,Read View會記錄此時所有其他活躍事務的快照,這些事務的修改對於當前事務都不可見的, 而早於Read View建立的事務所做的修改均是可見。 3)、在RC級別下,事務中,每次快照讀都會新生成一個快照和Read View,這就是我們在RC級別下的事務中可以看到別的事務提交的更新的原因。
總結∶在RC隔離級別下,是每個快照讀都會生成並獲取最新的Read View,而在RR隔離級別下,則是同一個事務中的第一個快照讀才會建立Read View,之後的快照讀獲取的都是同一個Read View。
樂觀鎖和MVCC區別
在資料庫中,併發控制是指在多個使用者/程序/執行緒同時對資料庫進行操作時,如何保證事務的一致性和隔離性的,同時最大程度地併發。
當多個使用者/程序/執行緒同時對資料庫進行操作時,會出現3種衝突情形:
- 讀-讀,不存在任何問題
- 讀-寫,有隔離性問題,可能遇到髒讀(會讀到未提交的資料) ,幻影讀等。
- 寫-寫,可能丟失更新
要解決衝突,一種辦法是是鎖,即基於鎖的併發控制,比如2PL,這種方式開銷比較高,而且無法避免死鎖。
多版本併發控制(MVCC)是一種用來解決讀-寫衝突的無鎖併發控制,也就是為事務分配單向增長的時間戳,為每個修改儲存一個版本,版本與事務時間戳關聯,讀操作只讀該事務開始前的資料庫的快照。 這樣在讀操作不用阻塞寫操作,寫操作不用阻塞讀操作的同時,避免了髒讀和不可重複讀
樂觀併發控制(OCC)是一種用來解決寫-寫衝突的無鎖併發控制,認為事務間爭用沒有那麼多,所以先進行修改,在提交事務前,檢查一下事務開始後,有沒有新提交改變,如果沒有就提交,如果有就放棄並重試。樂觀併發控制類似自選鎖。樂觀併發控制適用於低資料爭用,寫衝突比較少的環境。
多版本併發控制可以結合基於鎖的併發控制來解決寫-寫衝突,即MVCC+2PL,也可以結合樂觀併發控制來解決寫-寫衝突。