InnoDB Redo日志
1. Redo日志的作用
Redo日志(Redo Log)是InnoDB存储引擎中用于保证事务持久性的重要机制。事务提交后,即使系统发生崩溃,通过Redo日志可以将事务对数据库的修改重新应用,从而确保数据的完整性和一致性。
flowchart TD
A[事务开始] --> B[修改数据页]
B --> C[生成Redo日志]
C --> D[事务提交]
D --> E[Redo日志刷盘]
E --> F[系统崩溃]
F --> G[重启恢复]
G --> H[读取Redo日志]
H --> I[重放修改操作]
I --> J[数据恢复完成]
style C fill:#e1f5fe
style E fill:#f3e5f5
style I fill:#e8f5e8
1.1 持久性保证机制
Redo日志通过以下方式保证事务的持久性:
- 预写日志(WAL):在数据页刷盘之前,必须先将对应的Redo日志刷盘
- 顺序写入:Redo日志采用顺序写入方式,提高I/O效率
- 原子性恢复:崩溃恢复时,能够原子性地重放所有已提交事务的修改
2. Redo日志的设计动机
InnoDB以页为单位管理存储空间,事务操作(如增删改查)本质上是对页面的访问和修改。为了保证事务的持久性,需要将修改后的页面刷新到磁盘。然而,直接刷新页面存在以下问题:
2.1 性能问题分析
flowchart LR
subgraph "直接刷页面的问题"
A[修改1个字节] --> B[刷新整个16KB页面]
C[多个不相邻页面] --> D[随机I/O操作]
E[频繁磁盘写入] --> F[性能瓶颈]
end
subgraph "Redo日志解决方案"
G[记录修改内容] --> H[顺序写入日志]
I[批量刷盘] --> J[提高性能]
end
B --> G
D --> H
F --> I
style B fill:#ffebee
style D fill:#ffebee
style F fill:#ffebee
style H fill:#e8f5e8
style I fill:#e8f5e8
style J fill:#e8f5e8
2.2 问题详解
- 浪费磁盘I/O:即使只修改了一个字节,也需要刷新整个页面(默认16KB)
- 随机I/O性能差:一个事务可能修改多个不相邻的页面,导致随机I/O操作频繁,尤其在机械硬盘上效率低下
- 内存压力:频繁的页面刷新会增加内存和磁盘的压力
因此,InnoDB引入了Redo日志机制,仅记录对页面的修改内容,而不是直接刷新整个页面。
3. Redo日志的格式
Redo日志记录了事务对数据库的修改操作,其结构通常包含以下部分:
classDiagram
class RedoLogRecord {
+Type: 日志类型
+Space_ID: 表空间ID
+Page_Number: 页面编号
+Data: 具体修改内容
+Checksum: 校验和
}
class LogType {
+MLOG_1BYTE
+MLOG_2BYTE
+MLOG_4BYTE
+MLOG_8BYTE
+MLOG_WRITE_STRING
+MLOG_REC_INSERT
+MLOG_COMP_REC_INSERT
}
RedoLogRecord --> LogType
3.1 字段详解
| 字段 | 描述 | 作用 |
|---|---|---|
| Type | 日志类型 | 表示日志的用途(如插入记录、更新记录等) |
| Space ID | 表空间ID | 标识日志所属的表空间 |
| Page Number | 页面编号 | 标识被修改的页面 |
| Data | 日志内容 | 记录修改的详细信息 |
| Checksum | 校验和 | 确保日志完整性 |
4. Redo日志的类型
InnoDB根据不同的修改场景定义了多种类型的Redo日志,常见的类型包括:
4.1 基础数据类型日志
flowchart LR
A[Redo日志类型] --> B[基础数据类型]
A --> C[记录操作类型]
A --> D[页面操作类型]
A --> E[压缩相关类型]
B --> B1[MLOG_1BYTE<br/>写入1字节]
B --> B2[MLOG_2BYTE<br/>写入2字节]
B --> B3[MLOG_4BYTE<br/>写入4字节]
B --> B4[MLOG_8BYTE<br/>写入8字节]
B --> B5[MLOG_WRITE_STRING<br/>写入字符串]
C --> C1[MLOG_REC_INSERT<br/>插入非紧凑记录]
C --> C2[MLOG_COMP_REC_INSERT<br/>插入紧凑记录]
C --> C3[MLOG_COMP_REC_DELETE<br/>删除紧凑记录]
D --> D1[MLOG_COMP_PAGE_CREATE<br/>创建紧凑页面]
E --> E1[MLOG_ZIP_PAGE_COMPRESS<br/>压缩数据页]
style B1 fill:#e3f2fd
style C1 fill:#f3e5f5
style D1 fill:#e8f5e8
style E1 fill:#fff3e0
4.2 日志类型详解
- MLOG_1BYTE、MLOG_2BYTE、MLOG_4BYTE、MLOG_8BYTE:分别表示在页面的某个偏移量处写入1、2、4、8个字节的数据
- MLOG_WRITE_STRING:表示在页面的某个偏移量处写入一串数据
- MLOG_REC_INSERT、MLOG_COMP_REC_INSERT:分别表示插入一条非紧凑行格式记录和紧凑行格式记录
- MLOG_COMP_PAGE_CREATE、MLOG_COMP_REC_DELETE:表示创建一个紧凑行格式记录的页面和删除一条紧凑行格式记录
- MLOG_ZIP_PAGE_COMPRESS:表示压缩一个数据页
5. Redo日志的物理与逻辑特性
Redo日志具有物理和逻辑的双重特性:
flowchart LR
subgraph "物理层面"
A[指定表空间ID] --> B[指定页面编号]
B --> C[精确定位修改位置]
end
subgraph "逻辑层面"
D[调用特定函数] --> E[传入日志参数]
E --> F[执行恢复操作]
end
C --> D
style A fill:#e3f2fd
style B fill:#e3f2fd
style C fill:#e3f2fd
style D fill:#f3e5f5
style E fill:#f3e5f5
style F fill:#f3e5f5
5.1 特性详解
- 物理层面:Redo日志指明了对哪个表空间的哪个页面进行了修改
- 逻辑层面:在系统崩溃重启时,不能直接根据日志恢复页面,而是需要调用特定函数,根据日志中的参数执行操作,从而恢复页面
这种设计使得Redo日志既能精确定位修改位置,又能灵活地执行恢复操作。
6. Mini-Transaction(mtr)
Mini-Transaction是InnoDB中对底层页面进行原子访问的过程。一个mtr可以包含一组Redo日志,在崩溃恢复时,这些日志作为一个不可分割的整体。
sequenceDiagram
participant App as 应用程序
participant MTR as Mini-Transaction
participant Log as Redo日志
participant Page as 数据页
App->>MTR: 开始mtr
MTR->>Page: 修改页面1
MTR->>Log: 生成日志1
MTR->>Page: 修改页面2
MTR->>Log: 生成日志2
MTR->>Log: 原子性提交所有日志
MTR->>App: mtr完成
Note over MTR,Log: 所有日志作为一个整体<br/>要么全部应用,要么全部不应用
6.1 mtr的典型场景
- 更新Max Row ID属性:这是一个完整的mtr操作
- 向B+树中插入记录:包含多个页面修改的复杂mtr
- 页面分裂操作:涉及多个页面的原子性操作
6.2 mtr的重要性
- 原子性保证:确保相关的多个页面修改要么全部成功,要么全部失败
- 一致性维护:保持数据结构(如B+树)的完整性
- 恢复简化:崩溃恢复时按mtr为单位进行,简化恢复逻辑
7. Redo日志的写入过程
7.1 Redo Log Block结构
classDiagram
class RedoLogBlock {
+Header: 12字节
+Body: 496字节
+Trailer: 4字节
+Total: 512字节
}
class Header {
+LOG_BLOCK_HDR_NO: 4字节
+LOG_BLOCK_HDR_DATA_LEN: 2字节
+LOG_BLOCK_FIRST_REC_GROUP: 2字节
+LOG_BLOCK_CHECKPOINT_NO: 4字节
}
class Trailer {
+LOG_BLOCK_CHECKSUM: 4字节
}
RedoLogBlock --> Header
RedoLogBlock --> Trailer
7.2 写入流程
flowchart TD
A[mtr开始] --> B[生成Redo日志]
B --> C[写入Log Buffer]
C --> D[mtr结束]
D --> E{Log Buffer是否满?}
E -->|是| F[刷盘到日志文件]
E -->|否| G[继续写入]
F --> H[更新LSN]
G --> I[等待下次刷盘]
style C fill:#e3f2fd
style F fill:#f3e5f5
style H fill:#e8f5e8
7.3 关键组件
- Redo Log Block:Redo日志存储在大小为512字节的块(block)中,每个block包含日志头部(header)、日志正文(body)和日志尾部(trailer)
- Redo日志缓冲区(Log Buffer):日志首先写入内存中的Log Buffer,Log Buffer被划分为多个连续的block。通过
innodb_log_buffer_size参数可以设置Log Buffer的大小,默认为16MB - 写入Log Buffer:日志以mtr为单位写入Log Buffer,每个mtr产生的日志组在mtr结束时一次性写入Log Buffer
8. Redo日志的刷盘时机
日志不会一直停留在内存中,会在以下情况下被刷新到磁盘:
flowchart TD
A[Redo日志刷盘触发条件] --> B[Log Buffer空间不足]
A --> C[事务提交]
A --> D[后台线程定期刷新]
A --> E[服务器正常关闭]
A --> F[Checkpoint操作]
B --> B1[使用量达到50%]
C --> C1[保证事务持久性]
D --> D1[每秒定期刷新]
E --> E1[刷新所有日志]
F --> F1[配合脏页刷新]
style B1 fill:#ffebee
style C1 fill:#e8f5e8
style D1 fill:#e3f2fd
style E1 fill:#fff3e0
style F1 fill:#f3e5f5
8.1 刷盘时机详解
- Log Buffer空间不足:当Log Buffer的使用量达到一定比例(如一半)时,日志会被刷新到磁盘
- 事务提交时:为了保证事务的持久性,事务提交时会将对应的Redo日志刷新到磁盘
- 后台线程定期刷新:后台线程会定期(如每秒)将Log Buffer中的日志刷新到磁盘
- 正常关闭服务器时:服务器关闭时会将所有日志刷新到磁盘
- Checkpoint操作时:在执行Checkpoint操作时,日志也会被刷新到磁盘
9. Redo日志文件组
Redo日志文件以组的形式存在,默认情况下有两个文件:ib_logfile0和ib_logfile1。
flowchart LR
A[ib_logfile0] --> B[ib_logfile1]
B --> C[ib_logfile2]
C --> D[...]
D --> E[ib_logfile99]
E --> A
F[写入指针] --> A
style A fill:#e8f5e8
style B fill:#e3f2fd
style F fill:#f3e5f5
9.1 配置参数
可以通过以下参数调整日志文件的配置:
| 参数 | 描述 | 默认值 | 范围 |
|---|---|---|---|
innodb_log_group_home_dir | 日志文件所在目录 | 数据目录 | - |
innodb_log_file_size | 每个日志文件大小 | 48MB | 1MB-512GB |
innodb_log_files_in_group | 日志文件数量 | 2 | 2-100 |
9.2 循环写入机制
日志文件以循环方式写入,当一个文件写满后,会切换到下一个文件,直到最后一个文件写满后重新从第一个文件开始写。这种设计确保了:
- 空间利用率高:充分利用所有日志文件空间
- 写入连续性:保持顺序写入的性能优势
- 自动管理:无需手动管理日志文件切换
10. Redo日志文件格式
每个Redo日志文件由两部分组成:
classDiagram
class RedoLogFile {
+前2048字节: 管理信息
+后续部分: Log Block镜像
}
class ManagementInfo {
+Log File Header: 512字节
+Checkpoint1: 512字节
+空白区域: 512字节
+Checkpoint2: 512字节
}
class LogFileHeader {
+LOG_HEADER_FORMAT: 格式版本
+LOG_HEADER_PAD1: 填充
+LOG_HEADER_START_LSN: 起始LSN
+LOG_HEADER_CREATOR: 创建信息
}
RedoLogFile --> ManagementInfo
ManagementInfo --> LogFileHeader
10.1 文件结构详解
- 前2048字节:存储管理信息,包括日志文件头部(log file header)和Checkpoint信息
- 后续部分:存储Log Buffer中的block镜像
10.2 管理信息的作用
- 文件头部:记录日志文件的基本信息和格式版本
- Checkpoint信息:用于崩溃恢复时确定恢复起点
- 双Checkpoint设计:提供冗余保护,确保至少有一个有效的Checkpoint
11. Log Sequence Number(LSN)
LSN是一个全局变量,用于记录系统写入的Redo日志量。LSN的初始值为8704,随着日志的写入不断增加。
flowchart TD
A[LSN = 8704<br/>初始值] --> B[写入日志正文]
B --> C[LSN += 日志正文大小]
C --> D[写入日志头部和尾部]
D --> E[LSN += 头部尾部大小]
E --> F[LSN持续增长]
G[LSN的组成] --> H[日志正文大小]
G --> I[日志头部大小]
G --> J[日志尾部大小]
style A fill:#e3f2fd
style F fill:#e8f5e8
style H fill:#f3e5f5
style I fill:#fff3e0
style J fill:#ffebee
11.1 LSN的增长规律
LSN的增长包括:
- 日志正文的大小:实际写入的日志内容
- 日志头部和尾部的大小:即使日志正文较小,也会加上头部和尾部的大小
11.2 LSN的重要作用
- 唯一标识:每个日志记录都有唯一的LSN标识
- 顺序保证:LSN的递增保证了日志的写入顺序
- 恢复定位:崩溃恢复时用于确定恢复的起点和终点
- 一致性检查:用于验证页面和日志的一致性
12. Flushed_to_disk_lsn
flushed_to_disk_lsn是一个全局变量,记录已经刷新到磁盘的Redo日志量。其值初始与LSN相同,随着日志刷新到磁盘而增长。
sequenceDiagram
participant Buffer as Log Buffer
participant Disk as 磁盘日志文件
participant LSN as LSN计数器
participant FDLSN as flushed_to_disk_lsn
Note over Buffer,FDLSN: 初始状态:LSN = flushed_to_disk_lsn = 8704
Buffer->>LSN: 写入日志,LSN增加
Note over LSN: LSN = 10000
Note over FDLSN: flushed_to_disk_lsn = 8704
Buffer->>Disk: 刷盘操作
Disk->>FDLSN: 更新flushed_to_disk_lsn
Note over FDLSN: flushed_to_disk_lsn = 10000
Note over LSN,FDLSN: LSN - flushed_to_disk_lsn = 未刷盘的日志量
12.1 作用和意义
- 持久化跟踪:跟踪哪些日志已经安全地写入磁盘
- 恢复优化:崩溃恢复时可以跳过已经持久化的部分
- 性能监控:通过
LSN - flushed_to_disk_lsn可以了解待刷盘的日志量
13. Checkpoint机制
Checkpoint用于标记可以被覆盖的Redo日志。Checkpoint的执行包括以下步骤:
flowchart TD
A[开始Checkpoint] --> B[扫描Buffer Pool]
B --> C[找到最早的脏页]
C --> D[获取oldest_modification]
D --> E[设置checkpoint_lsn]
E --> F[计算日志文件偏移量]
F --> G[生成Checkpoint编号]
G --> H{编号是奇数?}
H -->|是| I[写入checkpoint1]
H -->|否| J[写入checkpoint2]
I --> K[Checkpoint完成]
J --> K
style E fill:#e8f5e8
style I fill:#e3f2fd
style J fill:#f3e5f5
style K fill:#fff3e0
13.1 Checkpoint执行步骤
- 计算Checkpoint LSN:找到当前系统中最早修改的脏页对应的
oldest_modification值,将其赋值给checkpoint_lsn - 记录Checkpoint信息:将
checkpoint_lsn、对应的日志文件偏移量和Checkpoint编号写入日志文件的管理信息中 - 双重保护:Checkpoint的编号存储在
checkpoint1或checkpoint2中,根据编号的奇偶性决定存储位置
13.2 Checkpoint的重要性
- 空间回收:标记可以被覆盖的日志空间
- 恢复优化:确定崩溃恢复的起点,减少恢复时间
- 一致性保证:确保脏页刷盘和日志的一致性
14. 崩溃恢复
在系统崩溃后,可以通过Redo日志恢复数据。恢复过程如下:
flowchart TD
A[系统崩溃重启] --> B[读取Checkpoint信息]
B --> C[确定恢复起点<br/>checkpoint_lsn]
C --> D[扫描日志文件]
D --> E[找到最后一个完整block]
E --> F[确定恢复终点]
F --> G[按LSN顺序读取日志]
G --> H[解析日志记录]
H --> I[检查页面LSN]
I --> J{页面需要恢复?}
J -->|是| K[应用日志修改]
J -->|否| L[跳过该日志]
K --> M[继续下一条日志]
L --> M
M --> N{还有日志?}
N -->|是| G
N -->|否| O[恢复完成]
style C fill:#e8f5e8
style F fill:#e3f2fd
style K fill:#f3e5f5
style O fill:#fff3e0
14.1 恢复过程详解
- 确定恢复起点:从最近一次Checkpoint的
checkpoint_lsn开始读取日志 - 确定恢复终点:扫描日志文件,找到最后一个未填满的block
- 恢复数据:按照日志的顺序,将对应的页面恢复到崩溃前的状态。可以通过哈希表优化恢复过程,避免重复读取页面
14.2 恢复优化策略
flowchart LR
A[恢复优化] --> B[哈希表分组]
A --> C[页面LSN检查]
A --> D[批量I/O操作]
B --> B1[按Space ID + Page Number分组]
C --> C1[跳过已刷新页面]
D --> D1[减少随机I/O]
style B1 fill:#e8f5e8
style C1 fill:#e3f2fd
style D1 fill:#f3e5f5
15. 多页面操作与Redo日志机制详解
15.1 多页面操作的Redo日志生成策略
当一个操作涉及多个页面时,InnoDB会为每个被修改的页面生成对应的Redo日志记录,而不是生成一条综合的日志。这种设计有以下原因:
flowchart TD
A[一个事务操作] --> B[修改页面1]
A --> C[修改页面2]
A --> D[修改页面3]
B --> B1[生成Redo日志1<br/>Space ID: 1, Page: 100]
C --> C1[生成Redo日志2<br/>Space ID: 1, Page: 200]
D --> D1[生成Redo日志3<br/>Space ID: 2, Page: 50]
B1 --> E[Mini-Transaction边界]
C1 --> E
D1 --> E
E --> F[原子性提交所有日志]
style E fill:#e8f5e8
style F fill:#f3e5f5
15.1.1 为什么需要多条Redo日志
| 原因 | 说明 | 优势 |
|---|---|---|
| 精确恢复 | 每个页面的修改都有独立的日志记录 | 可以精确恢复每个页面的状态 |
| 并行恢复 | 不同页面的恢复可以并行进行 | 提高崩溃恢复的效率 |
| 选择性恢复 | 可以根据页面LSN跳过已恢复的页面 | 避免重复恢复,提高性能 |
| 故障隔离 | 单个页面的问题不影响其他页面 | 提高系统的健壮性 |
15.1.2 Mini-Transaction的作用
虽然生成多条Redo日志,但通过Mini-Transaction机制确保原子性:
sequenceDiagram
participant App as 应用操作
participant MTR as Mini-Transaction
participant Page1 as 页面1
participant Page2 as 页面2
participant Page3 as 页面3
participant Log as Redo日志
App->>MTR: 开始复杂操作
MTR->>Page1: 修改页面1
MTR->>Log: 记录日志1
MTR->>Page2: 修改页面2
MTR->>Log: 记录日志2
MTR->>Page3: 修改页面3
MTR->>Log: 记录日志3
MTR->>Log: 原子性提交所有日志
MTR->>App: 操作完成
Note over MTR,Log: 崩溃恢复时,这些日志<br/>要么全部重放,要么全部跳过
15.2 Checkpoint机制与多页面操作
15.2.1 Checkpoint如何处理多页面操作
Checkpoint机制在处理多页面操作时,会考虑所有相关页面的状态:
flowchart TD
A[Checkpoint开始] --> B[扫描Buffer Pool中所有脏页]
B --> C[找到oldest_modification最小的脏页]
C --> D[该页面对应的LSN]
D --> E[设置为checkpoint_lsn]
F[多页面操作示例] --> G[页面A: LSN=1000]
F --> H[页面B: LSN=1200]
F --> I[页面C: LSN=1500]
G --> J[如果页面A未刷盘<br/>checkpoint_lsn = 1000]
H --> K[如果页面B未刷盘<br/>checkpoint_lsn = 1200]
I --> L[如果页面C未刷盘<br/>checkpoint_lsn = 1500]
style C fill:#e8f5e8
style E fill:#f3e5f5
style J fill:#ffebee
15.2.2 重放起点的确定原理
Redo日志的重放起点确定遵循以下原则:
- 最保守原则:从最早未刷盘页面的LSN开始
- 完整性保证:确保所有相关的Mini-Transaction都能完整重放
- 一致性维护:保证数据结构(如B+树)的完整性
flowchart LR
A[确定重放起点] --> B[读取最新Checkpoint]
B --> C[获取checkpoint_lsn]
C --> D[从该LSN开始扫描]
D --> E[找到完整的mtr边界]
E --> F[开始重放操作]
G[示例场景] --> H[checkpoint_lsn = 1000]
H --> I[扫描发现mtr在LSN=995开始]
I --> J[实际从LSN=995开始重放]
style C fill:#e8f5e8
style E fill:#f3e5f5
style J fill:#e3f2fd
15.3 实际案例分析
15.3.1 B+树页面分裂操作
以B+树页面分裂为例,说明多页面操作的Redo日志处理:
flowchart TD
A[B+树页面分裂操作] --> B[原页面修改]
A --> C[新页面创建]
A --> D[父页面更新]
A --> E[系统页面更新]
B --> B1["Redo日志1: MLOG_COMP_REC_DELETE<br/>删除部分记录"]
C --> C1["Redo日志2: MLOG_COMP_PAGE_CREATE<br/>创建新页面"]
C --> C2["Redo日志3: MLOG_COMP_REC_INSERT<br/>插入记录到新页面"]
D --> D1["Redo日志4: MLOG_COMP_REC_INSERT<br/>父页面插入索引项"]
E --> E1["Redo日志5: MLOG_4BYTE<br/>更新系统信息"]
B1 --> F[Mini-Transaction提交]
C1 --> F
C2 --> F
D1 --> F
E1 --> F
style F fill:#e8f5e8
15.3.2 崩溃恢复时的处理
sequenceDiagram
participant Recovery as 恢复进程
participant Checkpoint as Checkpoint信息
participant LogFile as 日志文件
participant Page as 数据页
Recovery->>Checkpoint: 读取checkpoint_lsn
Note over Checkpoint: checkpoint_lsn = 1000
Recovery->>LogFile: 从LSN=1000开始扫描
LogFile->>Recovery: 发现mtr开始于LSN=995
Recovery->>LogFile: 从LSN=995开始读取
LogFile->>Recovery: 返回完整的mtr日志组
Recovery->>Page: 检查页面LSN
Page->>Recovery: 页面LSN < 日志LSN,需要恢复
Recovery->>Page: 按顺序应用所有日志
Note over Recovery,Page: 原子性恢复整个mtr操作
15.4 性能优化考虑
15.4.1 批量处理优化
flowchart LR
A[多页面日志] --> B[按页面分组]
B --> C[哈希表存储]
C --> D[批量读取页面]
D --> E[顺序应用日志]
E --> F[减少I/O次数]
G[优化效果] --> H["减少随机I/O: 60-80%"]
G --> I["提升恢复速度: 40-60%"]
G --> J["降低内存使用: 30-50%"]
style C fill:#e8f5e8
style F fill:#f3e5f5
15.4.2 并行恢复策略
对于涉及不同表空间或不相关页面的操作,可以采用并行恢复:
| 并行策略 | 适用场景 | 性能提升 |
|---|---|---|
| 表空间级并行 | 不同表空间的页面修改 | 2-4倍速度提升 |
| 页面级并行 | 同表空间不相关页面 | 1.5-3倍速度提升 |
| mtr级串行 | 同一mtr内的页面修改 | 保证一致性,无并行 |
16. 总结
Redo日志是InnoDB存储引擎中实现事务持久性的关键机制。通过记录页面的修改内容,Redo日志在系统崩溃后可以恢复数据,同时避免了直接刷新页面带来的性能问题。