1. 程式人生 > >Mysql MVCC實現原理解析

Mysql MVCC實現原理解析

 

MVCC(Multi-Version Concurrency Control | 多版本併發控制) 
InnoDB通過為每一行記錄新增兩個額外的隱藏的值來實現MVCC,這兩個值一個記錄這行資料何時被建立,另外一個記錄這行資料何時過期(或者被刪除)。但是InnoDB並不儲存這些事件發生時的實際時間,相反它只儲存這些事件發生時的系統版本號(LSN)。這是一個隨著事務的建立而不斷增長的數字。每個事務在事務開始時會記錄它自己的系統版本號。每個查詢必須去檢查每行資料的版本號與事務的版本號是否相同。

Undo log是MySQL Innodb引擎的日誌的一種,記錄了老版本的資料

。 
Undo log是Innodb MVCC重要組成部分,InnoDB的MVCC就是基於Undo log實現的。

當我們對資料進行操作的時候,就會產生undo記錄,Undo記錄預設記錄在系統表空間(ibdata)中,從MySQL 5.6開始,Undo使用的表空間可以分離為獨立的Undo log檔案

在Innodb當中,INSERT操作在事務提交前只對當前事務可見,Undo log在事務提交後即會被刪除,因為新插入的資料沒有歷史版本,所以無需維護Undo log。而對於UPDATE、DELETE,責需要維護多版本資訊。 
在InnoDB當中,UPDATE和DELETE操作產生的Undo log都屬於同一型別:update_undo。(update可以視為insert新資料到原位置,delete舊資料,undo log暫時保留舊資料)

Undo log的作用

有了MVCC,InnoDB就能實現一致性非鎖定讀

舉個例子:

Session1(以下簡稱S1)和Session2(以下簡稱S2)同時訪問(不一定同時發起,但S1和S2事務有重疊)同一資料A,S1想要將資料A修改為資料B,S2想要讀取資料A的資料。

如果沒有MVCC,那麼事情的發展可能是這樣的:

  1. S1先執行,修改資料A,資料頁被鎖,S2等待A修改完後讀取新的資料。
  2. S2先執行,讀取資料A,資料頁被加讀鎖 ,S1想要加X鎖失敗,等待S2讀取完畢後,修改資料A。
  3. S1、S2同時加鎖,互斥,進入spin狀態再重試,直到有一方加鎖成功,後重現1或2。

如果,併發訪問量不是2,而是兩百、兩千呢? 
這無疑對資料庫的效能有著非常嚴重的影響。

所以,InnoDB儲存引擎通過多版本控制的方式來讀取當前執行時間資料庫中行的資料,如果讀取的行正在執行DELETE或UPDATE操作,這是讀取操作不會因此等待行上鎖的釋放。相反的,InnoDB會去讀取行的一個快照資料(Undo log)。

在InnoDB當中,要對一條資料進行處理,會先看這條資料的版本號是否大於自身事務版本(非RU隔離級別下當前事務發生之後的事務對當前事務來說是不可見的),如果大於,則從歷史快照(undo log鏈)中獲取舊版本資料,來保證資料一致性。

而由於歷史版本資料存放在undo頁當中,對資料修改所加的鎖對於undo頁沒有影響,所以不會影響使用者對歷史資料的讀,從而達到非一致性鎖定讀,提高併發效能。

 

      innodb MVCC主要是為Repeatable-Read事務隔離級別做的。在此隔離級別下,A、B客戶端所示的資料相互隔離,互相更新不可見 
瞭解innodb的行結構、Read-View的結構對於理解innodb mvcc的實現由重要意義 
innodb儲存的最基本row中包含一些額外的儲存資訊 DATA_TRX_ID,DATA_ROLL_PTR,DB_ROW_ID,DELETE BIT 
6位元組的DATA_TRX_ID 標記了最新更新這條行記錄的transaction id,每處理一個事務,其值自動+1 
7位元組的DATA_ROLL_PTR 指向當前記錄項的rollback segment的undo log記錄,找之前版本的資料就是通過這個指標 
6位元組的DB_ROW_ID,當由innodb自動產生聚集索引時,聚集索引包括這個DB_ROW_ID的值,否則聚集索引中不包括這個值.,這個用於索引當中 
DELETE BIT位用於標識該記錄是否被刪除,這裡的不是真正的刪除資料,而是標誌出來的刪除。真正意義的刪除是在commit的時候. 
這裡寫圖片描述
具體的執行過程 
begin->用排他鎖鎖定該行->記錄redo log->記錄undo log->修改當前行的值,寫事務編號,回滾指標指向undo log中的修改前的行 
上述過程確切地說是描述了UPDATE的事務過程,其實undo log分insert和update undo log,因為insert時,原始的資料並不存在,所以回滾時把insert undo log丟棄即可,而update undo log則必須遵守上述過程 
下面分別以select、delete、 insert、 update語句來說明 
SELECT 
Innodb檢查每行資料,確保他們符合兩個標準: 
1、InnoDB只查詢版本早於當前事務版本的資料行(也就是資料行的版本必須小於等於事務的版本),這確保當前事務讀取的行都是事務之前已經存在的,或者是由當前事務建立或修改的行 
2、行的刪除操作的版本一定是未定義的或者大於當前事務的版本號,確定了當前事務開始之前,行沒有被刪除 
符合了以上兩點則返回查詢結果。 
INSERT 
InnoDB為每個新增行記錄當前系統版本號作為建立ID。 
DELETE 
InnoDB為每個刪除行的記錄當前系統版本號作為行的刪除ID。 
UPDATE 
InnoDB複製了一行。這個新行的版本號使用了系統版本號。它也把系統版本號作為了刪除行的版本。 
說明 
insert操作時 “建立時間”=DB_ROW_ID,這時,“刪除時間 ”是未定義的; 
update時,複製新增行的“建立時間”=DB_ROW_ID,刪除時間未定義,舊資料行“建立時間”不變,刪除時間=該事務的DB_ROW_ID; 
delete操作,相應資料行的“建立時間”不變,刪除時間=該事務的DB_ROW_ID; 
select操作對兩者都不修改,只讀相應的資料 
對於MVCC的總結 
上述更新前建立undo log,根據各種策略讀取時非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,這個可能與我們所理解的MVCC有較大的出入,一般我們認為MVCC有下面幾個特點: 
每行資料都存在一個版本,每次資料更新時都更新該版本 
修改時Copy出當前版本隨意修改,各個事務之間無干擾 
儲存時比較版本號,如果成功(commit),則覆蓋原記錄;失敗則放棄copy(rollback) 
就是每行都有版本號,儲存時根據版本號決定是否成功,聽起來含有樂觀鎖的味道,而Innodb的實現方式是: 
事務以排他鎖的形式修改原始資料 
把修改前的資料存放於undo log,通過回滾指標與主資料關聯 
修改成功(commit)啥都不做,失敗則恢復undo log中的資料(rollback) 
二者最本質的區別是,當修改資料時是否要排他鎖定,如果鎖定了還算不算是MVCC? 
Innodb的實現真算不上MVCC,因為並沒有實現核心的多版本共存,undo log中的內容只是序列化的結果,記錄了多個事務的過程,不屬於多版本共存。但理想的MVCC是難以實現的,當事務僅修改一行記錄使用理想的MVCC模式是沒有問題的,可以通過比較版本號進行回滾;但當事務影響到多行資料時,理想的MVCC據無能為力了。 
比如,如果Transaciton1執行理想的MVCC,修改Row1成功,而修改Row2失敗,此時需要回滾Row1,但因為Row1沒有被鎖定,其資料可能又被Transaction2所修改,如果此時回滾Row1的內容,則會破壞Transaction2的修改結果,導致Transaction2違反ACID。 
理想MVCC難以實現的根本原因在於企圖通過樂觀鎖代替二段提交。修改兩行資料,但為了保證其一致性,與修改兩個分散式系統中的資料並無區別,而二提交是目前這種場景保證一致性的唯一手段。二段提交的本質是鎖定,樂觀鎖的本質是消除鎖定,二者矛盾,故理想的MVCC難以真正在實際中被應用,Innodb只是借了MVCC這個名字,提供了讀的非阻塞而已。