Redo與Undo的理解

本文概要數據庫

本文分兩部分,
第一部分概念介紹,重在理解。
第二部分經過MySQL Innodb中的具體實現,加深相關知識的印象。

本文的原意是一篇我的學習筆記,爲了不成爲草草記錄一下的流水帳,嘗試從給人介紹的角度開寫。但在整理的過程當中,愈來愈感受力不從心,一是細節太多了,原覺得足夠了解的一個小知識點下可能隱藏了不少細節;二是內容與範圍的取捨,既想有點技術性避免空談,又不想陷入枯燥冗長的小細節描述。幾番折騰,目前的想法把坑填上,能寫完就不錯了,你讀起來有不順或錯誤的地方請見諒,歡迎反饋。數組

 

1. 概念與理解安全

Redo與undo並不是是相互的逆操做,而是能配合起來使用的兩種機制。
說是兩種機制,其實都是日誌記錄,不一樣的是redo記錄以順序附加的形式記錄新值,如某條記錄<T,X,V>,表示事物T將新值V存儲到數據庫元素X,新值能夠保證重作;
而Undo記錄一般以隨機操做的形式記錄舊值,如某條記錄<T1,Y,9>,表示事物T1對Y進行了修改,修改前Y的值是9,舊值能用於撤銷,也能供其餘事務讀取。
Redo用來保證事務的原子性和持久性,Undo能保證事務的一致性,二者也是系統恢復的基礎前提。併發

 

1.1 Redo
一個事務從開始到結束,要麼提交完成,要麼停止,具備原子性。而反映在redo日誌中可能須要若干條記錄來保證,如:函數

<T0 start>
<T0,A,500>
<T0,B,500>
<T0 commit>

 

這裏的某條redo記錄不是事務級別的,通常對應的是事務中的一些關鍵步驟。如Innodb執行事務時會拆分爲不少小事務,每一個小事務產生某條redo記錄。
而經過幾個數據庫原語能更通常性的描述redo記錄:性能

Input(X):將X值從存儲介質讀入緩衝區
Read(X,t):將X值從緩衝區讀入事務內的變量t,若是緩衝中不存在,則觸發Input
Write(X,t): 將事務內的t寫入到緩衝區X塊,若是緩衝中X不存在,則觸發Input(X),再完成write
Output(X):將緩衝區X寫入到存儲中

因此上面的redo記錄用原語表示以下:

學習



很明顯,現實中的redo日誌大多不是這樣孤立的,更多的是多個事務交織在一塊兒的,錯誤也隨時能發生,從小到數據格式錯誤到機房被導彈炸了。
下面經過3個redo日誌來討論:

優化

(a) 在日誌中只有部分記錄,可能事務在執行時系統發生了崩潰,這時須要根據日誌重作(如下統一使用術語redo)。
(b) 日誌中T0已經提交了,必需要對T0 進行redo,而部分T1也須要redo
(c) 日誌中T0已經提交了,必需要對T0進行redo,而T1雖然abort也須要redospa

可能有人有疑惑,commit的事務確實要redo,但進行到一半未提交的事務及後來abort的事務能夠沒必要進行redo。確實,在日誌中的每個事務最終應該或者有一條commit記錄,或者有一條abort記錄,徹底能篩選出目標事務再redo,但這樣增長了redo階段的複雜性,因此是根據日誌統一redo,以後的撤銷工做交給undo來進行。這也是redo具備事務無關性的一個體現。

線程

1.2 Checkpoint
檢查點的引入有好幾個方面的緣由。

原則上,系統恢復時能夠經過檢查整個日誌來完成,但不管redo仍是undo,當日志很長時:
1.搜索過程太耗時
除了上面這點,針對redo而言還有:
2.儘管redo是冪等的,大多數須要重作的事務已經把更新寫入,對其重作不會有不良後果,但這會使恢復過程變得很長。
針對undo日誌:
3.一旦事務commit日誌記錄寫入磁盤,邏輯上而言本事務的undo記錄在恢復時已經不須要,在commit時能夠刪除以前的undo記錄。但因爲多事務同時執行的緣由,有時候不能這樣作,儘管本事務已經commit,但其餘事務可能在使用undo中的舊值。爲此須要checkpoint來處理這些當前活躍的事務。


檢查點技術可分爲簡單檢查點與更優化的非靜止檢查點。在一個簡單檢查點中有以下過程:
(1)中止接受新的事務
(2)等待當前全部活躍事務完成或停止,並在日誌中寫入commit或abort記錄。
(3)將當前位於內存的日誌,將緩衝塊刷新到磁盤
(4)寫入日誌記錄<CKPT>,並再次刷新到磁盤
(5)從新開始接受事務
系統恢復時,能夠從日誌尾端反向搜索,直到找到第一個<CKPT>標誌,而沒有必要處理<CKPT>以前的記錄。

非靜止檢查點
簡單檢查點期間須要中止響應,若是當前活躍事務須要很長時間來處理,那系統看起來彷佛卡住了。非靜止檢查點容許進行檢查點時接受新事務進入,步驟以下:
(1)寫入日誌記錄<START CKPT(t1,…tn)>,其中t1,…tn是當前活躍的事務
(2)等待t1,…tn全部事務提交或停止,但仍接受新事務的進入
(3)當t1,…tn全部事務都已完成,寫入日誌記錄<END CKPT>

 

當使用非靜止檢查點技術,恢復時的也是從日誌尾向前掃描,可能先遇到<START CKPT>標誌,也可能先遇到<END CKPT>標誌:
1.先遇到<START CKPT(t1,…tn)>時,說明系統在檢查點過程當中崩潰,未完成事務包括2部分:(t1,…tn)記錄的部分及<START>標誌後新進入部分。這部分事務中最先那個事務的開始點就是掃描的截止點,再往前能夠沒必要掃描。

2.先遇到<END CKPT>,說明系統完成了上一個週期的檢查點,新的檢查點還沒開始。須要處理2部分事務:<END CKPT>標誌以後到系統崩潰時這段時間內的事務及上一個<START>,<END>區間內新接受的事務。爲此掃描到上一個檢查點<START CKPT()>就能夠截止。

多說一句,很容易發現,非靜止檢查點是將一個點擴展爲一個處理區間了,相似的設計其餘技術也有,如JVM的GC處理,從stop the world到安全區的處理[1]。

 

 

1.3 Undo
Undo是邏輯日誌,並不冪等,在撤銷時,根據undo記錄進行補償操做。Undo自己也產生redo記錄。經過undo日誌數據庫能夠實現MVCC。
Undo保證了事務失敗或主動abort時的機能,除此以外,系統崩潰恢復時,也確保數據庫狀態能恢復到一致。

系統恢復時,undo須要redo的配合來實現,或者說兩者是一套機制的兩個方面。由於在redo日誌有commit或abort記錄的事務是無需undo的。
假設以靜止的檢查點爲日誌類型,以<CKPT (t0,…,tn)>作檢查點,期間不接受新事務進入,整個undo過程能夠描述以下:
1.以進行檢查點時記錄的活躍事務(t0,…,tn)爲undo-list
2.在redo階段,發現一條<T,START>記錄,就將T加入到undo-list
3.在redo階段,發現一條<T,END>或<T,ABORT>記錄,就將T從undo-list刪除
4.此時undo-list中的事務都是些未提交也沒回滾的事務,系統如同普通的事務回滾樣進行具體的undo操做
5.當undo-list中發現<T,START>時,說明完成了具體的回滾操做,系統寫入一個<T,ABORT>記錄,並從undo-list中刪除T。
6.直到undo-list爲空,撤銷階段完成

 

undo的原語表示能夠以下:

 

1.4 寫日誌
寫日誌有2種處理:一是等待一次IO,確實得寫入到存儲介質。二是先寫入到緩衝,在以後的某一時間點統一寫入磁盤。

以fsync函數與sync爲例:
fsync函數等待磁盤操做結束,而後返回,它能確保數據持久化到存儲介質,而不是停留在OS或存儲的寫緩衝中;
sync則把修改過的塊緩衝區排入OS的寫隊列後就返回。fsync能確保數據寫入,同時,這也意味着一次IO及性能消耗。


不一樣的數據庫部件有各自的設計目的,負責不一樣的命令,Read和Write由事務發起,Input和Output由緩衝區管理器發出。也就是說,日誌記錄響應的是寫入內存的write命令,而不是寫入磁盤的output命令,除非顯示的控制。
具體的實現上會有不少策略,但應保證一些原則:

針對undo
1.若是事務T改變了數據庫元素X,那麼必須保證對應的一條undo記錄在X的新值寫入磁盤以前落盤。
2.若是發生commit,那麼該條commit記錄寫入磁盤前,全部以前的修改能確保先行落盤。

針對redo,有一條先寫日誌規則(Write-Ahead Logging,WAL):
1.對數據庫元素X的修改被寫入磁盤前,一條對應的redo日誌保證先行落盤。
2.提交時,修改的數據庫元素在寫入磁盤前,一條commit記錄保證落盤。

注意這裏說的數據庫元素X,不是事務層面的更新記錄集,一般假定是一個最小的原子處理單位,一個磁盤塊。當某塊在output時,不能有對該塊的write。爲此在某塊輸出時能夠在塊上設置排他鎖,這種短時間持有的閂鎖(latch)與事務併發控制的鎖無關,按照非兩階段的方式釋放這樣的鎖對於事務可串行性沒有影響。若是數據庫元素小於單個塊,一個糟糕的情景是不一樣事務的2個數據元素位於同一塊,這時候一個事務對塊的寫磁盤動做可能致使另外一個事務違反寫入規則,一個建議是以塊做爲數據庫元素。

在InnoDB的實現中,並不嚴格按照WAL規則,而是經過一種事務的序列編號LSN保證邏輯上的WAL。下面對InnoDB的一些實現細節嘗試分析下。

 



2.MySQL InnoDB中的實現

2.1 redo log
每一個Innodb存儲引擎至少有一個重作日誌文件組(group),每一個文件組下至少有2個重作日誌文件,如默認的ib_logfile0和ib_logfile1,其默認路徑位於引擎的數據目錄。

 

設置多個日誌文件時,其名字以ib_logfile[num]形式命名。多個日誌文件循環利用,第一個文件寫滿時,換到第二個日誌文件,最後一個文件寫滿時,回到第一個文件,組成邏輯上無限大的空間。在Innodb1.2.x前,重作日誌文件的總大小不能大於等於4GB,1.2.x版本該限制以擴大到512GB.

 

重作日誌文件設置的越大,越能夠減小checkpoint刷新髒頁的頻率,這有時候對提高MySQL的性能很是重要,但缺點是增長了恢復時的耗時;若是設置的太小,則可能須要頻繁地切換文件,甚至一個事務的日誌要屢次切換文件,致使性能的抖動。

 

Innodb中各類不一樣的操做有着不一樣類型的重作日誌,類型數量有幾十種,但記錄條目的基本格式能夠以下表示:


圖2.1

 

在存儲結構上,redo log文件以block塊來組織,每一個block大小爲512字節。每一個文件的開頭有一個2k大小的File Header區域用來保存一些控制信息,File Header以後就是連續的block。雖然每一個redo log文件在頭部劃出了File Header區域,但實際存儲信息的只有group中第一個redo log文件。

圖2.2

 

當redo log實際由mtr(Mini transaction)產生時,首先位於mtr的cache,以後輸出到redo log 緩衝區,再從緩衝區寫入到磁盤。Log buffer與文件中的block大小對應,以512字節爲單位對齊,一個mtr日誌可能不足一個block,也可能跨block。

 

File Header
File Header位於每一個redo log文件的開始,大小爲2k,格式以下:

圖2.3

log group中的第一個文件實際存儲這些信息,其餘文件僅保留了空間。在寫入日誌時,除了完成block部分,還要更新File Header裏的信息,這些信息對Innodb引擎的恢復操做很是關鍵。

Block
一個block塊有512字節大小,每塊中還有塊頭和塊尾,中間是日誌自己。其中塊頭Block Header佔有12字節大小,塊尾Block Trailer佔有4字節大小,中間實際的日誌存儲容量爲496字節(512-12-4):


圖2.4

 

LOG_BLOCK_HDR_NO
在log buffer內部,能夠當作是單位大小是512字節的log block組成的數組,LOG_BLOCK_HDR_NO就用來標記數組中的位置。其根據該塊的LSN計算轉換而來,遞增且循環使用,佔有4個字節,第一位用來判斷是否flush bit,因此總容量是2G。(LSN在以後一段說明)

LOG_BLOCK_HDR_DATA_LEN
標識寫入本block的日誌長度,佔有2個字節,當寫滿時用0X200表示,即有512字節。

LOG_BLOCK_FIRST_REC_GROUP
佔有2個字節,記錄本block中第一個記錄的偏移量。若是該值與LOG_BLOCK_HDR_DATA_LEN
相同,說明此block被單一記錄佔有,不包含新的日誌。若是有新日誌寫入,LOG_BLOCK_FIRST_REC_GROUP就是新日誌的位置。

圖2.5

 

LOG_BLOCK_CHECKPOINT_NO
佔有4字節,記錄該block最後被寫入時檢查點第4字節值。

LOG_BLOCK_TRL_NO
Block trailer中只由這1個部分組成。記錄本block中的checksum值,與LOG_BLOCK_HDR_NO值相同。

 

LSN
LSN是Log Sequence Number的縮寫,佔有8字節,單調遞增,記錄重作日誌寫入的字節總量,也表示日誌序列號。

LSN除了記錄在redo日誌中,還存於每一個頁中。頁的頭部有一個FIL_PAGE_LSN用於記錄該頁的LSN,反應的是頁的當前版本。

LSN一樣也用於記錄checkpoint的位置。使用SHOW ENGINE INNODB STATUS命令查看LSN狀況時,Log sequence number是當前LSN,Log flushed up to 是刷新到重作日誌文件的LSN,Last checkpoint at 是刷新到磁盤的LSN。

因爲LSN具備單調增加性,若是重作日誌中的LSN大於當前頁中LSN,說明頁是滯後的,若是日誌記錄的LSN對應的事務已經提交,那麼當前頁須要重作恢復。
若是頁被新事務修改了,頁中LSN記錄的是新寫入的結束點的LSN,大於重作日誌中的LSN,那麼當前頁是新數據,是髒頁。
髒頁根據提交狀況可能須要加入flush list中,此時flush list上的因此髒頁也是以LSN排序。

寫redo log時是追加寫,須要保證寫入順序,或者說應保證LSN的有序。當併發寫時能夠經過加鎖來控制順序但效率低下,8.0中使用了無鎖的方式完成併發寫,mtr寫時已經提早知道本身在log buffer上的區間位置,沒必要等待直接寫入log buffer就可。這樣大的LSN值可能先寫到log buffer上,而小的LSN還沒寫入,即log buffer上有空洞。因此有一個單獨的線程log_write,負責不斷的掃描log buffer,檢測新的連續內容並進行刷新,是真正的寫線程。

 

2.2 Undo

undo是邏輯日誌,在事務回滾時對數據庫進行一些補償性的修改,以使數據在邏輯上回到修改前的樣子,它並不冪等。
在Innodb中使用表空間,回滾段,頁等多級概念結構實現undo功能,並隨版本屢次改進,爲方便討論,下面放一張5.7版本的大體結構圖,在此基礎上進行描述:


圖2.6

 

1. 在undo這部分,MySQL 5.7版本在5.6(InnoDB 1.2)的基礎上主要增長有innodb_undo_log_truncate 收縮等功能,但在大體結構方面5.6能夠參考上面5.7的圖。

2. 在5.5(Innodb1.1)版本以前,只有一個undo回滾段(rollback segment),支持1024個事務同時在線。

3.在5.5版中,支持最大128個回滾段,理論上支持128*1024個事務同時在線。

4.在以前的版本中,回滾段都存儲於共享表空間中,一個常見的問題是ibdata膨脹。在5.6版本(Innodb1.2)時,能夠對回滾段作更多的設置:
innodb_undo_directory
innodb_undo_logs
innodb_undo_tablespaces
這3個參數分別用來設置
(1)回滾段文件所在位置,這意味着回滾段能夠存儲到共享表空降值外,能使用獨立的表空間。
(2)回滾段的數量,默認是128個。
(3)回滾段文件的數量。如設置爲3個,則在上面指定的directory文件生成3個undo爲前綴的文件:undo001,undo002,undo003,默認的128個回滾段將被依次平均分配到這3個文件中。具體分配時,老是從第一個space開始輪詢,因此若是將回滾段的數量依次遞增到128,那全部的段都將落入undo001中。


5. 如上圖,共享表空間偏移量爲5的頁記錄有全部回滾段的指向信息,這頁的類型爲FIL_PAGE_TYPE_SYS(trx_sys)。 0號回滾段被預留在ibdata中,1~32號的32個回滾段是臨時表的回滾段,存儲於ibtmpl文件,其他從33號開始的回滾段纔是可配置的,所以InnoDB實際支出96*1024個普通事務同時在線。

6.每一個回滾段的頭部維護着一個段頭頁,該頁中劃分了1024個槽位slot(TRX_RSEG_N_SLOTS),每一個slot能夠對應一個undo log對象,這也是爲何說一個回滾段支持1024個事務。

7.MySQL8.0中,每一個Undo tablespace均可以建立128個回滾段,因此總共能夠有總共有innodb_rollback_segments * innodb_undo_tablespaces個回滾段。

 

結構體
回滾段的信息以數組的形式存放,數組大小爲128,數組位於trx_sys->rseg_array
rseg_array數組中的元素類型是trx_rseg_t,表示一個回滾段。
每一個trx_rseg_t中管理着許多trx_undo_t,這些trx_undo_t同時也屬於多個鏈表,不一樣的鏈表有着不一樣的功能,如insert_undo_list或update_undo_list等。

圖2.7

 


undo log格式


Innodb中undo log能夠分爲兩種:
inser undo log
update undo log


insert undo log是insert操做中產生的undo log,由於只對本事務可見,該類undo log在事務提交後就能夠刪除,不須要進行purge操做。格式以下:

圖2.8

 

update undo log是delete和update操做產生的undo log。此類undo log是MVCC的基礎,在本事務提交後不能簡單的刪除,須要放入purge隊列purge_sys->purge_queue
等待purge線程進行最後的刪除。格式以下:

圖2.9

圖上可見update undo log的格式比insert undo log複雜,同名的部分功能相似,其中的type_cmpl部分,因爲update undo log自己還有分類,因此值可能有:
TRX_UNDO_DEL_MARK_REC,將記錄標記爲delete
TRX_UNDO_UPD_DEL_REC,將delete的記錄標記爲not delete
TRX_UNDO_UPD_EXIST_REC,更新未被標記delete的記錄

 

--完--

相關文章
相關標籤/搜索