1. 程式人生 > >MySQL InnoDB redo Log 淺析

MySQL InnoDB redo Log 淺析

pre 數據庫事務 ins row 進行 res logfile def 技術分享

MySQL的InnoDB存儲引引擎的物理文件存儲體系中,除了實際的數據文件(ibd, ibdata)之外,還有兩個非常重要的日誌系統,分別是redo日誌和undo日誌。 跟Oracle類似, redo log記錄了對實際數據文件的物理變更(數據文件的什麽位置數據做了如何的變更)。InnoDB也是采用了WAL(日誌優先落盤),也就是說在實際數據文件的修改落盤之前redo日誌已經落盤,從而來保證事務的持久性。Undo日誌用來保證事務的原子性和MVCC,所有的undo操作產身的數據文件的變更也會記錄到redo日誌中。
在原生的MySQL中,redo日誌不會用來做物理主從復制,其主要的應用場景是用來進行MySQL的Crash Recovey(崩潰恢復)。關於MySQL InnoDB的崩潰恢復會在後續的文章中進行介紹。
本文主要基於MySQL 8.0介紹redo Log的基本構成。
1. redo 日誌文件
從MySQL 5.6開始,已經廢棄了日誌組的特性(redo日誌可以寫多份),網上有觀點認為可能是InnoDB的開發團隊認為用外層的存儲硬件來保證日誌組的完整性可能更好一些。同時從5.7開始InnoDB的歸檔日誌Archive也被放棄(歸檔日誌用來歸檔存放所有的redo日誌,redo日誌系統內是采用固定尺寸的多個log文件循環寫的方式來存放redo日誌,如果寫滿了會循環到開始的位置開始寫入)。
不過MySQL 8.0引入一個稱之為克隆的機制,從代碼的角度來看,似乎是用來實現遠程克隆一個當前數據庫的副本,在這個機制中又引入的新的Archive歸檔機制。如果讀者有興趣可以閱讀一下MySQL8.0新版本源碼的storage\innobase\arch和 storage\innobase\clone目錄下的代碼。
1.1. redo Log相關參數
innodb_log_group_home_dir
該參數用來指定redo日誌存放的路徑,日誌文件以ib_logfile[number]來命名。
innodb_log_files_in_group
雖然MySQL已經放棄了日誌組的概念,但參數名依舊保留了下來以兼容以前的配置。該參數的含義為有多少個log文件(最少為2個)。
innodb_log_file_size
表示每個文件的大小。
所以,總的redo Log的大小為 innodb_log_files_in_group * innodb_log_file_size.

1.2. redo Log循環寫
redo Log以順序的方式寫入文件,當全部文件寫滿的時候則回到第一個文件相應的起始位置進行覆蓋寫(但在做redo checkpoint時,也會更新第一個日誌文件的頭部checkpoint標記,所以嚴格來講也不算順序寫),在InnoDB內部,邏輯上Redo Log被看作一個文件,對應一個space id (InnoDB通過space的概念來組織物理存儲,包括不同的表,數據字典,redo,undo等)。

技術分享圖片

上圖是以指定innodb_log_files_in_group為3的循環寫的情況。

2. Redo Log存儲格式簡介
盡管Redo Log有多個文件,但每個文件的組成結構是一樣的,只是有一些數據只會存在的第一個Log文件(ib_logfile0)的文件頭中, 例如Buffer Pool flush checkpoint信息只會寫在第一個log文件的文件頭中。
2.1. 日誌文件存儲結構概覽
技術分享圖片
(ib_logfile0 存儲概覽圖)

技術分享圖片
(ib_logfile0 之外其它redo log文件的存儲概覽)
InnoDB也是采用WAL的機制來保證事務的持久性,從一定的意義上來說,redo日誌是順序寫,寫入速度很快。數據庫事務導致的數據修改進入到InnoDB存儲層之後會將這些修改變更的記錄存入redo log中,然後將數據的變更寫入內存中的Page Pool中。InnoDB的後臺線程會按照一定的規則(例如定時或者臟頁的數量達到一定的比例)將臟頁落盤,落盤後會記錄下來當前redo Log中有多少變更日誌已經實際存儲到了實際的數據space文件中。redo log的總的寫入量叫LSN(Log Secquence Numer)日誌序列號,這個redo log變更實際寫入到實際數據文件中的數量叫checkpoint LSN,表示的是有多少變更已經實際寫入到了相應的數據文件中。 一旦數據庫崩潰InnoDB開始恢復數據的時候,先讀取checkpoint,然後從checkpoint所指示的LSN讀取其之後的Redo日誌進行數據恢復,從而減少Crash Recovery的時間。
比較前面兩個概覽圖可以看到,checkpoint信息只是存儲在第一個log文件頭中。同時我們看到日誌頭中有2個checkpoint block域。InnoDB是采用2個checkpoint了輪流寫的方式來保證checkpoint的安全(並不是一次寫2份checkpoint, 而是輪流寫)。 也由於Redo log是冪等的,應用一次和與應用兩次都是一樣的(在實際的應用Redo中,如果當前page的LSN大於等於當前redo日誌的LSN,那麽就應用,否則就不應用)。所以,即使某次checkpoint block寫失敗了,那麽崩潰恢復的時候從上一次記錄的checkpoint點開始恢復也能正確的恢復數據庫事務。
2.2. Log File Header
技術分享圖片
(Log File Header存儲結構)
Log Header Format:
這個字段以前用來標識當前Log文件屬於哪個日誌組,現在新的意義是用來標識當前Log為文件的格式版本。例如如果為0則表示這個redo log是有5.7.9以前的MySQL生成的。
PAD:
沒有任何含義,目前僅僅用來做一些對齊處理。
Start LSN:
這個字段用在Clone和Archive場景,與一般的持久性、崩潰恢復無關,這裏不做討論。
Creator:
存儲的是創建這個log文件創建者的名稱的字符串。
Left Bytes:
目前沒有任何含義,僅僅是用來填充占位,以便讓這個block達到512字節大小。
2.3. Log Checkpoint
技術分享圖片
(Checkpoint block儲存概覽)
checkpoint number:
checkpoint number可以理解為checkpoint域寫盤的次數,每次寫盤遞增1,同時這個值取模2可以實現2個checkpoint域輪流寫。
checkpoint LSN:
該字段標示小於這個Checkpoint LSN的日誌記錄都已經寫入到了實際的數據文件中,Crash Recovery系統從Checkpoint LSN之後的第一個MTR記錄開始進行數據恢復。
checkpoint offset:
Checkpoint LSN對應在Log files中的文件偏移量,這個用來對LSN和Offset之間轉換進行校準。
Buf size:
MySQL系統內只對該字段執行了寫入,並為進行讀取然後進行相應的處理。它標識的是系統當前Log buffer的大小。
Left bytes:
目前沒有任何含義,僅僅是用來填充占位,以便讓這個block達到512字節大小。但在這裏最後4個字節用來存放該checkpoint域的Checksum。

2.4. Log Block的存儲格式
技術分享圖片
(Log Block的存儲格式)
Log Block Number:
Log Block的編號,從1開始遞增,達到最大值(0x3FFFFFFF+1)後再繼續從1開始。
Data length:
寫入到當前block的字節數,包含頭部12字節的大小
Firsrt Record offset
本Block內第一個mtr記錄的起始偏移量
log Block Checkpoint number
該block所處在的checkpoint no
Log Records:
一個block內可以存儲多條mtr記錄,同樣一個mtr記錄可以跨越多個block.
2.5. redo日誌邏輯格式到物理格式的映射
技術分享圖片
上圖是以指定innodb_log_files_in_group為2邏輯結構到物理結構的映射

上圖中上層的為Redolog的邏輯結構,可以看作是內存中的log buffer, 下層的為redo Log的實際物理文件存儲,由於版面的關系,我們以innodb_log_files_in_group為2來做示例,每個log文件中僅僅包含了2個log block(Log block的多少取決與設定的innodb_log_file_size)。
每產生一個mtr記錄就將其append到log buffer中去,當log buffer落盤的時候會獲取固定大小的數據寫入到block的數據域。當然,如果buffer中剩余的數據不足以填滿一個block的數據域,也會將其寫入到一個新的block中,不足的數據自動不齊,block header中的data length字段會指出有效數據的數量。

2.6. MTR簡介
MTR即Mini-transaction的縮寫,字面意思小事物,相對邏輯事物而言,我們把它稱作物理事物。屬於Innodb存儲引擎的底層模塊。主要用於鎖和日誌信息。InnoDB內部的上層模塊會將事務操作轉換成若幹的MTR物理事務。至於上層的事務操作如何轉換的MTR此操作,後續再另外單獨介紹,本文只介紹一下MTR記錄的格式。
每一個MTR操作會產身一條MTR Record, 下一小節我們會介紹一下MTR記錄的格式。
2.7. MTR記錄格式
用一句通俗的話來講,一條MTR記錄表示的是對哪個數據文件(space id)的哪一頁(page)的頁內某個偏移量(offset)位置做了什麽改變(value).
技術分享圖片

(一條MTR記錄的通用格式)
Type:
MTR記錄的類型
Space ID:
該MTR記錄修改了哪個數據文件
Page Number
該MTR記錄修改了哪一頁
Record Payload:
根據Type的不同,Payload內容格式也不相同,大小也不相同。後面會給出一個Type為MLOG_COMP_REC_INSERT大致的存儲結構。
(MTR Type - MLOG_COMP_REC_INSERT)
技術分享圖片
最後列出一些MTR Record Type, 讀者從名字應該就能看出這個Type的含義
/ one byte is written */
MLOG_1BYTE = 1,
/* 2 bytes ... /
MLOG_2BYTES = 2,
/
4 bytes ... */
MLOG_4BYTES = 4,
/ 8 bytes ... */
MLOG_8BYTES = 8,
/* Record insert /
MLOG_REC_INSERT = 9,
/
Mark clustered index record deleted */
MLOG_REC_CLUST_DELETE_MARK = 10,
/ Mark secondary index record deleted */
MLOG_REC_SEC_DELETE_MARK = 11,
/* update of a record, preserves record field sizes /
MLOG_REC_UPDATE_IN_PLACE = 13,
/!< Delete a record from a page /
MLOG_REC_DELETE = 14,
/
Delete record list end on index page */
MLOG_LIST_END_DELETE = 15,
/ Delete record list start on index page */
MLOG_LIST_START_DELETE = 16,
/* Copy record list end to a new created index page /
MLOG_LIST_END_COPY_CREATED = 17,
/
Reorganize an index page in ROW_FORMAT=REDUNDANT */
MLOG_PAGE_REORGANIZE = 18,
/ Create an index page */
MLOG_PAGE_CREATE = 19,
/* mark a compact index record as the predefined minimum record /
MLOG_COMP_REC_MIN_MARK = 36,
/
create a compact index page */
MLOG_COMP_PAGE_CREATE = 37,
/* compact record insert /
MLOG_COMP_REC_INSERT = 38,
/* mark compact clustered index record deleted /
MLOG_COMP_REC_CLUST_DELETE_MARK = 39,

本文由京東商城數據庫技術部王治提供。

MySQL InnoDB redo Log 淺析