redo日志
作用
innoDB存儲引擎中,需要在服務器故障重啟后,能夠准確的恢復所有已提交的數據,保證數據持久性;如某個事務在內存Buffer Pool中已被提交(臟頁),但服務器突然故障,數據就丟失了;
為了解決這個問題,可以采用修改頁面刷新到磁盤,但因為可能只修改了一條記錄,沒必要實時刷新浪費時間,而且修改的記錄並不一定是連續的,隨機IO刷新較慢。
可以將已提交事務修改的記錄記錄下來,即某個表空間中某頁的某個偏移量的值更新為多少,這個記錄的文件就稱為redo log。相比刷新內存中的頁面到磁盤,redo log刷新到磁盤的內容小了很多,而且是一個順序寫入磁盤的過程。
redo日志不止記錄索引插入/更新記錄等操作,還有執行這個操作影響到的其他動作,如頁分裂新增目錄項記錄,修改頁信息等對數據頁做的任何修改等等。
和binlog區別:binlog記錄的是頁已經正式落盤的操作且是包含所有存儲引擎,redo日志記錄InnoDB引擎下仍然在buffer pool中的操作,用於系統奔潰時恢復臟頁。
日志
日志格式
-
type:類型
- MLOG_1BYTE:1 :表示在頁面的某個偏移量處寫入1個字節的
redo
日志類型。 - MLOG_2BYTE:2
- MLOG_4BYTE:4
- MLOG_8BYTE:8
- MLOG_WRITE_STRING:30
- MLOG_REC_INSERT:9:表示插入一條使用非緊湊行格式記錄時的redo日志類型(如redundant)
- MLOG_COMP_REC_INSERT:38:表示插入一條使用非緊湊行格式記錄時的redo日志類型(如compact/dynamic/compressed)
- MLOG_COMP_REC_DELETE:42:表示刪除一條使用緊湊行格式記錄的
redo
日志類型 - MLOG_COMP_LIST_START_DELETE和MLOG_COMP_LIST_END_DELETE:批量刪除,可以很大程度上減少redo日志的條數
- MLOG_1BYTE:1 :表示在頁面的某個偏移量處寫入1個字節的
-
space id:表空間
-
page number:頁號
-
data:真實數據(以MLOG_COMP_REC_INSERT為例)
- n_fileds:當前記錄的字段數
- n_uniques:決定該記錄的唯一字段列數量,如有主鍵的是主鍵數,唯一二級索引是索引列數+主鍵列數,普通二級索引一樣;插入時可根據這個字段進行排序
- field1_len-fieldn_len:若干字段占用存儲空間的大小
-
offset:記錄前一條記錄在頁面中的位置,方便修改頁面中的記錄鏈表,前一條記錄維護的next_record屬性
- end_seg_len:通過這個字段可得知當前記錄占用存儲空間大小
-
len:類型為MLOG_WRITE_STRING時才有的,表示具體數據占用的字節數
redo日志內存內操作
Mini-Transaction
Mini-Transaction(mtr)是對底層頁面中的一次原子訪問的過程(如MAX_ROW_ID
生成/索引記錄插入)。一個事務可以包含多個mtr,一個mtr包含多條redo日志。
以組的形式寫入日志
針對要保證原子性的操作必須以組的形式來記錄redo日志,以插入一條記錄為例,當出現頁分裂時,會涉及到申請數據頁,改動系統頁,改各種段/區的統計信息,free等鏈表的信息,新增目錄項記錄等等操作,要么全都執行完成要么全都沒執行。
划分日志組:通過為這一系列動作的日志的最后一條日志后添加一個特別的日志,類型為MLOG_MULTI_REC_END:31
作為結尾。
如果某個原子操作只有一條日志記錄,那么給這條日志類型的第一個比特位設為1,不然就是產生了一系列日志。
redo日志寫入過程
MySQL使用512字節的頁來記錄redo日志,這種頁被稱為block
,block
由log block header
,log block trailer
和log block body
組成,header和trailer存儲頁的管理信息。
服務器啟動時會先向系統申請一片連續的內存作為redo日志緩沖區log buffer,以兩個事務為例,事務T1和T2是交替執行的,所以可能是交替存儲到log buffer的。它們各自包含兩個mtr,每個mtr在運行過程中,會先將redo log存在一個地方,等到這個mtr執行結束,就會將這個mtr產生的所有redo日志全部復制到log buffer,一定時間后才會沖刷到磁盤。
向log buffer
中寫入日志是順序的,所以block不會出現碎片空間,InnoDB提供一個全局變量buf_free
來指明后續寫入的redo日志應該往block的哪個位置中寫入,即從這個位置開始后面都是空閑的。
redo日志刷盤
刷盤准備
當修改buffer pool
中的頁時,會將這個臟頁的控制塊插入到flush鏈表中,控制塊存儲了兩個變量:oldest_modification
被加載到buffer pool
中第一次修改mtr開始時對應的lsn值,newest_modification
每次mtr修改結束時對應的lsn值;控制塊按照oldest_modification
從大到小排序存儲。
一個mtr可能修改多個頁,所以多個控制塊的oldest_modification
/newest_modification
可能一樣。
刷盤時機
- log buffer空間不足,空閑空間小於一半時
- 事務提交時,buffer pool中的臟頁可以先不刷盤,但其中的log buffer需要刷盤,防止丟失
- 后台線程定時刷盤
- 正常關閉服務器時
- checkpoint時:批量從flush鏈表中刷出臟頁:如果系統修改頁面頻繁,且不能將臟頁刷出,則不能及時checkpoint,可能會直接使用用戶線程同步的從flush鏈表中最早修改的臟頁刷盤,這樣這些臟頁對應的redo日志就沒用了,就可以checkpoint了
redo日志文件存儲
在MySQL的數據目錄下,(由innodb_log_group_home_dir
確定存儲位置,由名稱可知存儲形式是一個日志文件組)名為ib_logfile0
...n的文件,文件個數決定文件名稱后綴,由系統參數innodb_log_files_in_group
確定文件個數,每個文件的大小由innodb_log_file_size
指定。
每個ib_logfile
順序循環寫入log buffer
中的block,會出現文件被覆蓋的現象。
日志文件格式
-
前2048個字節(4個block):存儲管理信息
-
log file header:存儲當前文件的redo日志版本,文件開始的LSN值等等
-
checkpoint1:標記日志文件可覆蓋信息
- LOG_CHECKPOINT_NO:checkpoint次數,遞增記錄
- LOG_CHECKPOINT_LSN:redo日志文件可被覆蓋的最大lsn值
- LOG_CHECKPOINT_OFFSET:lsn對應日志文件的偏移量
- LOG_CHECKPOINT_LOG_BUF_SIZE
-
沒用
-
checkpoint2
-
-
后面的字節:存儲block內容,會被循環使用
幾個全局變量
-
Log Sequeue Number(lsn):InnoDB記錄已寫入的redo log量的全局變量,包括log buffer寫入的日志,初始值為8704,此時日志文件的偏移量為2048字節。
- 第一次一個mtr生成一組日志並加入到log buffer時,lsn=8704+日志寫入量+log block header
- 再次生產一組日志時,在同一個block 內,且能容納,就只需要加上日志寫入量;
- 又生成一組日志,但占用量較大,當前block中剩余空間不可容納,需要占用到下下個block時,lsn=lsn+日志寫入量+2 *log block header+2 *log block trailer;
-
flushed_to_disk_lsn:刷新到磁盤中的redo日志量的全局變量
-
buf_next_to_write:標記已有哪些log buffer中的日志被刷盤的全局變量
-
innodb_flush_log_at_trx_commit=?:表示用戶線程提交時需要將該事務執行過程中產生的所有redo日志刷盤到磁盤(1)還是交給后台線程操作(0),或者先寫入到緩沖區中(2)。
checkpoint
判斷某些redo日志占用的磁盤空間是否可以被覆蓋,即它對應的臟頁是否已經刷新到磁盤
checkpoint_lsn
:代表當前系統中可以被覆蓋(臟頁已經被刷盤)的redo日志總量,初始值為8074。
checkpoint步驟:
- 計算可以被覆蓋的redo日志對應的lsn最大值(flush鏈表最小
oldest_modification
值對應的lsn之前的日志都是可以被覆蓋掉的,因為flush鏈表不存儲已經被刷盤的臟頁),賦值給checkpoint_lsn
; - 將
checkpoint_lsn
對應的redo日志文件組偏移量checkpoint_offset
及此次checkpoint
的編號(總共做了多少次checkpoint,遞增)checkpoint_no
寫到日志文件頭部的checkpoint1/2
中
奔潰恢復
恢復奔潰發生時,flush鏈表中還未寫入磁盤的臟頁更改。
-
確定恢復的起點:比較日志中文件中
checkpoint1
和checkpoint2
中最大的checkpoint_no,然后從對應的checkpoint_lsn
(之前的都是可覆蓋的,說明已經被刷盤了)開始恢復 -
確定恢復的終點:比較每個日志文件
log block header
的LOG_BLOCK_HDR_DATA_LEN
屬性,如果不為512則說明是最后一個填充的日志文件 -
恢復方法
- 哈希表:計算redo日志的
hash(表空間id+頁號)
,相同值的放在一個slot中,並根據生成時間排序鏈表形式連接,這樣可以一次修復一個頁面,避免多余的隨機IO - 跳過奔潰時已被恢復的頁:flush鏈表中可能存在已經被刷盤的臟頁,所以會根據臟頁的file header中FIL_PAGE_LSN屬性即控制塊中的
newest_modification
是否大於checkpoint_lsn
,如果是那么就不需要執行小於newest_modification
的FIL_PAGE_LSN
的redo日志了
- 哈希表:計算redo日志的