MySql MVCC實現原理解析

MYSQL 事務日誌

事務日誌可以幫助提高事務的效率。使用事務日誌,存儲引擎在修改表的數據時只需要修改其內存拷貝再把該修改行爲記錄到持久在硬盤上的事務日誌中,而不用每次都將修改的數據本身持久到磁盤。事務日誌採用的是追加的方式,因此寫日誌的操作是磁盤上一小塊區域內的順序I/O,而不像隨機I/O需要在磁盤的多個地方移動磁頭,所以採用事務日誌的方式相對來說要快得多。事務日誌持久以後,內存中被修改的數據在後臺可以慢慢地刷回到磁盤。目前大多數存儲引擎都是這樣實現的,我們通常稱之爲預寫式日誌(Write-Ahead Logging),修改數據需要寫兩次磁盤
如果數據的修改已經記錄到事務日誌並持久化,但數據本身還沒有寫回磁盤,此時系統崩潰,存儲引擎在重啓時能夠自動恢復這部分修改的數據。

所以 採用日誌的方式的好處:

1、系統異常場景的恢復數據

2、把多次對數據的操作由每次對磁盤操作,改爲對內存數據的修改後順序I/O 再刷數據回磁盤 提高效率

MySQL Innodb中跟數據持久性、一致性有關的日誌,有以下幾種:

  • Bin Log:是mysql服務層產生的日誌,常用來進行數據恢復、數據庫複製,常見的mysql主從架構,就是採用slave同步master的binlog實現的
  • Redo Log:記錄了數據操作在物理層面的修改,mysql中使用了大量緩存,修改操作時會直接修改內存,而不是立刻修改磁盤,事務進行中時會不斷的產生redo log,在事務提交時進行一次flush操作,保存到磁盤中。當數據庫或主機失效重啓時,會根據redo log進行數據的恢復,如果redo log中有事務提交,則進行事務提交修改數據。
  • Undo Log: 除了記錄redo log外,當進行數據修改時還會記錄undo log,undo log用於數據的撤回操作,它記錄了修改的反向操作,比如,插入對應刪除,修改對應修改爲原來的數據,通過undo log可以實現事務回滾,並且可以根據undo log回溯到某個特定的版本的數據,實現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? 但由於Mysql的寫操作會加排他鎖
Innodb的實現真算不上MVCC,因爲並沒有實現核心的多版本共存,undo log中的內容只是串行化的結果,記錄了多個事務的過程,不屬於多版本共存。但理想的MVCC是難以實現的,當事務僅修改一行記錄使用理想的MVCC模式是沒有問題的,可以通過比較版本號進行回滾;但當事務影響到多行數據時,理想的MVCC據無能爲力了。 
比如,如果Transaciton1執行理想的MVCC,修改Row1成功,而修改Row2失敗,此時需要回滾Row1,但因爲Row1沒有被鎖定,其數據可能又被Transaction2所修改,如果此時回滾Row1的內容,則會破壞Transaction2的修改結果,導致Transaction2違反ACID。 
理想MVCC難以實現的根本原因在於企圖通過樂觀鎖代替二段提交。修改兩行數據,但爲了保證其一致性,與修改兩個分佈式系統中的數據並無區別,而二段提交是目前這種場景保證一致性的唯一手段二段提交的本質是鎖定,樂觀鎖的本質是消除鎖定,二者矛盾,故理想的MVCC難以真正在實際中被應用,Innodb只是借了MVCC這個名字,提供了讀的非阻塞而已

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章