logs
UndoLog 日志
与 ACID 的关系
| 特性 | 实现机制 | 说明 |
|---|---|---|
| 原子性 (Atomicity) | undo log | 事务回滚时恢复到修改前的状态 |
| 一致性 (Consistency) | 由其他三个特性共同保证 | 是目标,不是手段 |
| 隔离性 (Isolation) | MVCC + 锁 | undo log 提供历史版本 |
| 持久性 (Durability) | redo log | 崩溃恢复,保证提交的数据不丢失 |
undo log 主要保证原子性(支持回滚),同时也参与隔离性(MVCC 版本链)
作用
UndoLog(回滚日志)是 InnoDB 存储引擎的日志,主要有两个作用:
- 事务回滚:当执行 ROLLBACK 时,根据 undo log 将数据恢复到修改前的状态
- MVCC 多版本并发控制:通过 undo log 构建数据的历史版本,支持快照读
类型
| 类型 | 触发操作 | 记录内容 | 生命周期 |
|---|---|---|---|
| insert undo log | INSERT | 只记录主键 | 事务提交后可立即删除 |
| update undo log | UPDATE / DELETE | 记录旧值 | 需等待没有事务引用后由 purge 线程删除 |
为什么生命周期不同?
- INSERT 的数据是新插入的,其他事务不可能看到这条新数据(隔离性),所以事务提交后 undo log 就没用了
- UPDATE/DELETE 修改的是已有数据,其他事务可能正在读取这条数据的旧版本(MVCC),必须保留 undo log 直到没有事务需要
三种操作的记录内容
| 操作 | undo log 记录内容 | 回滚操作 |
|---|---|---|
| INSERT | 只记录主键 | DELETE 该行 |
| UPDATE | 记录被修改列的旧值 | UPDATE 为旧值 |
| DELETE | 记录整行数据 | 恢复该行(清除删除标记) |
设计原则:记录回滚所需的最少信息
版本链
每行数据有三个隐藏字段:
| 字段 | 说明 |
|---|---|
| DB_TRX_ID | 最后修改该行的事务 ID |
| DB_ROLL_PTR | 回滚指针,指向 undo log 中的上一个版本 |
| DB_ROW_ID | 隐藏主键(无主键时自动生成) |
每次修改数据时,会将旧值写入 undo log,并让新记录的 roll_ptr 指向这条 undo log,形成版本链:
最新数据 (trx_id=300)
│ roll_ptr
▼
undo log v2 (trx_id=200)
│ roll_ptr
▼
undo log v1 (trx_id=100)
│ roll_ptr
▼
NULL与 MVCC 的关系
MVCC 读取时,根据 Read View 的可见性规则,沿着版本链找到对当前事务可见的版本:
trx_id == creator_trx_id→ 可见(自己修改的)trx_id < min_trx_id→ 可见(早已提交)trx_id >= max_trx_id→ 不可见(之后创建的事务)min ≤ trx_id < max:在 m_ids 中不可见,不在则可见
与 redo log 的关系
undo log 本身也是写在数据页(undo 页)中的,对 undo 页的修改也需要 redo log 保护:
- 修改 undo 页
- 写 redo log(记录对 undo 页的修改)
- 然后才能修改数据页
崩溃恢复时:先用 redo log 恢复 undo 页,然后 undo log 可用于回滚未提交事务。
注意事项
避免长事务:长事务会导致 undo log 无法被 purge 线程清理,造成:
- undo log 空间暴涨
- 查询变慢(需要遍历很长的版本链)
RedoLog 日志
与 ACID 的关系
redo log 主要保证 持久性 (Durability):事务一旦提交,数据就不会丢失(即使系统崩溃)。
作用
- 崩溃恢复:系统崩溃后,通过 redo log 恢复已提交但未刷盘的数据
- 提升性能:通过 WAL 机制,将随机写转换为顺序写
WAL 机制
WAL(Write-Ahead Logging)= 先写日志,后写数据
1. 修改 Buffer Pool 中的数据页(内存操作,快)
2. 写 redo log(顺序写磁盘,快!)
3. 事务提交成功 ✅
4. 后台异步刷数据页到磁盘(不着急)顺序写比随机写快几个数量级(10-100 倍)
特点
| 特点 | 说明 |
|---|---|
| 物理日志 | 记录"在某个数据页的某个偏移量做了什么修改" |
| 顺序写 | 追加写入,性能高 |
| 循环写 | 固定大小(默认 2 个文件各 48MB),写满后从头覆盖 |
| InnoDB 特有 | 只有 InnoDB 有 redo log |
循环写入机制
- write_pos:当前写入位置,边写边后移
- checkpoint:已刷盘的脏页位置
- 可写区域:checkpoint → write_pos 之间
write_pos 追上 checkpoint 时,必须暂停写入,先刷脏页推进 checkpoint
刷盘策略
通过 innodb_flush_log_at_trx_commit 控制:
| 值 | 行为 | 性能 | 安全性 |
|---|---|---|---|
| 0 | 每秒刷盘(后台线程) | 最高 | 可能丢 1 秒数据 |
| 1 | 每次提交都 fsync(默认) | 最低 | 最安全 |
| 2 | 每次提交写 OS cache,每秒 fsync | 中等 | OS 崩溃可能丢数据 |
额外的刷盘时机(无论策略设置为何):
| 时机 | 说明 |
|---|---|
| 后台线程每秒刷盘 | InnoDB 后台 log 线程每秒执行一次刷盘 |
| redo log buffer 满了 | buffer 空间不足时自动刷盘 |
| checkpoint | 推进 checkpoint 时触发刷盘 |
即使
innodb_flush_log_at_trx_commit=0,后台线程也会每秒刷一次,所以最多丢失 1 秒数据
写入时机
| 时机 | 写入内容 |
|---|---|
| 执行 SQL 时 | 数据页的物理修改记录(写入 redo log buffer) |
| 事务提交时 | prepare/commit 标记 + 刷盘(根据策略) |
边执行边写(到 buffer),提交时刷盘
Mini-Transaction (mtr)
一个 SQL 可能修改多个数据页,生成多条 redo log 记录。为保证原子性:
- 一组相关的 redo log 记录作为一个 mtr
- 最后写结束标记
MLOG_MULTI_REC_END - 恢复时只处理有结束标记的 mtr
崩溃恢复
崩溃恢复两步:
1. redo 前滚:从 checkpoint 开始重放 redo log,恢复所有修改
2. undo 回滚:根据事务状态,回滚未提交的事务与 undo log 的关系
undo log 本身也是写在数据页(undo 页)中的,对 undo 页的修改也需要 redo log 保护。
崩溃恢复时:先用 redo log 恢复 undo 页,然后 undo log 可用于回滚未提交事务。
BinLog 日志
基本定义
binlog = 二进制日志(归档日志)
属于 Server 层的日志(所有存储引擎都有),主要用于:
- 主从复制
- 数据恢复(基于时间点恢复 PITR)
三种格式
| 格式 | 记录内容 | 优点 | 缺点 |
|---|---|---|---|
| STATEMENT | SQL 语句本身 | 日志量小 | 动态函数(如 NOW())可能导致主从不一致 |
| ROW | 每行数据的变更 | 数据绝对一致 | 日志量大(批量修改时记录多条) |
| MIXED | 自动选择 | 结合两者优点 | 可能会增加复杂度 |
生产环境推荐 ROW 格式,以保证数据一致性。
刷盘策略
通过 sync_binlog 控制:
| 值 | 行为 | 说明 |
|---|---|---|
| 0 | 只 write 到 OS cache,由 OS 决定何时 fsync | 性能最高,OS 崩溃可能丢数据 |
| 1 | 每次事务提交都 fsync(默认) | 最安全 |
| N | 每 N 个事务 fsync 一次 | 折中方案 |
注意:binlog 没有后台定时刷盘机制,完全依赖
sync_binlog配置。
写入流程
- 事务执行中:写入线程私有的 binlog cache(内存)
- 事务提交时:write 到 binlog 文件(OS page cache)
- 刷盘:根据
sync_binlog决定何时 fsync 到磁盘
如果 cache 超过
binlog_cache_size(默认 32KB),会写入临时文件
两阶段提交与崩溃恢复
为什么需要二阶段提交?
为了保证 redo log(InnoDB 层)和 binlog(Server 层)的一致性。如果两者不一致,会导致主从数据不一致(主库用 redo 恢复,从库用 binlog 同步)。
详细流程
1. Prepare 阶段 (InnoDB)
- 写入 redo log,标记为 prepare 状态
- 刷盘 (fsync)
2. 写 Binlog 阶段 (Server)
- 写入 binlog
- 刷盘 (fsync)
3. Commit 阶段 (InnoDB)
- 写入 redo log,标记为 commit 状态
- (这一步不需要立即 fsync,因为 binlog 已经完整)崩溃恢复逻辑
MySQL 重启时,扫描 redo log,根据事务状态和 XID(内部事务 ID)决定如何处理:
| redo log 状态 | binlog 状态 | 处理操作 | 结果 |
|---|---|---|---|
| commit | (不用管) | 提交 | 事务成功 ✅ |
| prepare | 完整 (有 XID) | 提交 | 事务成功 ✅ |
| prepare | 不完整 (无 XID) | 回滚 | 事务取消 ❌ |
| (无记录) | (无记录) | 回滚 | 事务取消 ❌ |
核心逻辑:只要 binlog 完整(已刷盘),事务就应该提交,确保主库和从库都有这条数据。
三大日志配合与恢复全流程
正常执行流程
- 写 undo log:记录数据修改前的样子(用于回滚)。
- 修改内存:更新 Buffer Pool 中的数据页。
- 写 redo log (buffer):记录数据页的物理修改。
- 提交事务 (2PC):
- redo log prepare
- binlog write
- redo log commit
崩溃恢复流程
Redo 前滚:
- 从 checkpoint 开始,重放 redo log。
- 恢复所有内存中未刷盘的脏页修改(包括对 undo 页的修改)。
- 结果:Buffer Pool 恢复到了崩溃前的内存状态(不管事务是否提交)。
Undo 回滚:
- 扫描 redo log 中处于 prepare 状态的事务。
- 检查 binlog 是否完整。
- 不完整的事务,利用 undo log 执行回滚操作(反向修改)。
主从复制详解
复制架构的三大线程
Binlog Dump Thread (主库)
- 监听从库连接,读取主库的 binlog 发送给从库。
- 实时推送,不是轮询。
I/O Thread (从库)
- 连接主库,接收 binlog 事件。
- 将日志写入从库的 relay log (中继日志)。
SQL Thread (从库)
- 读取 relay log。
- 解析并执行 SQL,将数据应用到从库。
复制模式
| 模式 | 说明 | 优缺点 |
|---|---|---|
| 异步复制 (默认) | 主库写完 binlog 即返回,不等待从库确认 | 性能高,主库宕机可能丢数据 |
| 半同步复制 (Semi-Sync) | 主库等待至少 1 个从库收到 binlog (写入 relay log) 才返回 | 数据不易丢失,性能稍差 |
| 全同步/组复制 (MGR) | 基于 Paxos 协议,多数派确认 | 强一致性,性能最低 |
组提交 (Group Commit)
解决的问题
传统 2PC 中,每个事务都要进行 2 次 fsync (redo prepare + binlog),高并发下 IO 是巨大瓶颈。
组提交机制
将多个并发事务的 binlog 合并,通过一次 fsync 写入磁盘。分为三个阶段:
Flush 阶段
- 收集并发提交的事务,按顺序写入 binlog buffer。
- 这里的事务成为一个"组"。
Sync 阶段
- 将 binlog buffer 中的数据 fsync 到磁盘。
- 一次 IO,多个事务受益。
Commit 阶段
- 依次对 redo log 进行 commit 标记。
性能优化参数
binlog_group_commit_sync_delay:等待多少微秒才 fsync,为了收集更多事务。binlog_group_commit_sync_no_delay_count:达到多少个事务就立即 fsync。
并行复制优化:主库能「组提交」的事务,说明没有锁冲突,从库也可以在 SQL 线程中并行执行这些事务,从而解决主从延迟问题。
