接續前文基本概念,本文深入源碼層面剖析 Redo Log Block 結構、頭尾部元數據、跨 Block 日誌寫入機制,為理解 WAL、檢查點、崩潰恢復奠定基礎。
InnoDB 以 512 位元組為單位管理 Redo Log(OS_FILE_LOG_BLOCK_SIZE),每個 Block 包含:
+------------------+------------------+------------------+------------------+ | Header (12 bytes) | Log Data (496 bytes) | Trailer (4 bytes) | +------------------+------------------+------------------+------------------+
#define LOG_BLOCK_HDR_NO 0 // Block 序號(4 bytes),最高位=1 表示 flush 寫入段首塊
#define LOG_BLOCK_HDR_DATA_LEN 4 // 本 Block 實際日誌資料長度(2 bytes)
#define LOG_BLOCK_FIRST_REC_GROUP 6 // 本 Block 內第一個 MTR 日誌組起始偏移(2 bytes),0=無
#define LOG_BLOCK_CHECKPOINT_NO 8 // 寫入時最新 checkpoint_no 低 4 位元組(4 bytes)
#define LOG_BLOCK_HDR_SIZE 12 // Header 總長
#define LOG_BLOCK_CHECKSUM 4 // CRC32 校驗和(4 bytes),從 Block 尾部向前
#define LOG_BLOCK_TRL_SIZE 4 // Trailer 總長
MTR (Mini-Transaction) 日誌可能跨 Block。當一個 MTR 日誌 > 496 bytes 時:
Block N: [Header] [Log Data: 前 496 bytes] [Trailer]
Block N+1: [Header] [剩餘 4 bytes] [新 MTR 從 offset 16 開始] [Trailer]
↑ LOG_BLOCK_FIRST_REC_GROUP = 16
作用:崩潰恢復時,從該偏移開始解析完整 MTR,避免從截斷處誤解析。
// 寫入時更新 FIRST_REC_GROUP
if (len == 0) {
// 本 Block 尚無完整 MTR,記錄潛在起始位置
mach_write_to_2(block + LOG_BLOCK_FIRST_REC_GROUP, LOG_BLOCK_HDR_DATA_LEN);
} else {
// 有完整 MTR 起始於此
mach_write_to_2(block + LOG_BLOCK_FIRST_REC_GROUP, len);
}
LOG_BLOCK_CHECKPOINT_NO 記錄寫入該 Block 時的最新 checkpoint 序號(低 32 位)。用於:
512 (Block) - 12 (Header) - 4 (Trailer) = 496 bytes Log Data
這也是為什麼大 MTR 必須跨 Block 寫入的原因。
MTR 1 (500 bytes) 寫入流程: 1. Block 100: Header + 496 bytes (MTR 1 前段) + Trailer FIRST_REC_GROUP = 0 (Block 100 內無新 MTR 起始) 2. Block 101: Header + 4 bytes (MTR 1 尾段) + 100 bytes (MTR 2 完整) + Trailer FIRST_REC_GROUP = 16 (MTR 2 起始於 offset 16,即 Header 12 + MTR 1 尾段 4)
log/log0log.h - 核心常數、結構定義log/log0log.cc - 日誌寫入、flush、檢查點主流程log/log0recv.cc - 崩潰恢復解析邏輯mtr/mtr0mtr.cc - MTR 生成與提交log/log0chkp.cc - 檢查點協調下文將分析:
innodb_flush_log_at_trx_commit 三種模式源碼實現理解 Redo Log Block 結構,是掌握 InnoDB 事務持久化、崩潰恢復、複制同步核心機制的鑰匙。