MySQL 实战精通系列 · 第6篇:InnoDB 存储引擎、日志与崩溃恢复
本篇目标:搞懂 InnoDB 怎么存数据、怎么写日志、崩溃后怎么恢复、怎么做备份。
文章目录
- [MySQL 实战精通系列 · 第6篇:InnoDB 存储引擎、日志与崩溃恢复](#MySQL 实战精通系列 · 第6篇:InnoDB 存储引擎、日志与崩溃恢复)
-
- [一、InnoDB 整体架构](#一、InnoDB 整体架构)
-
- [1.1 InnoDB 内存 + 磁盘结构](#1.1 InnoDB 内存 + 磁盘结构)
- [1.2 各组件作用](#1.2 各组件作用)
- [二、Buffer Pool 缓冲池](#二、Buffer Pool 缓冲池)
-
- [2.1 为什么需要 Buffer Pool?](#2.1 为什么需要 Buffer Pool?)
- [2.2 Buffer Pool 工作流程](#2.2 Buffer Pool 工作流程)
- [2.3 LRU 改进版](#2.3 LRU 改进版)
- [2.4 查看 Buffer Pool 状态](#2.4 查看 Buffer Pool 状态)
- [三、三大日志:Redo / Undo / Binlog](#三、三大日志:Redo / Undo / Binlog)
-
- [3.1 日志总览](#3.1 日志总览)
- [3.2 Redo Log](#3.2 Redo Log)
- [3.3 Undo Log](#3.3 Undo Log)
- [3.4 Binlog](#3.4 Binlog)
- [3.5 三日志对比](#3.5 三日志对比)
- [四、WAL 机制:为什么写日志比写数据快?](#四、WAL 机制:为什么写日志比写数据快?)
-
- [4.1 WAL 原理](#4.1 WAL 原理)
- [4.2 图解](#4.2 图解)
- [4.3 为什么快?](#4.3 为什么快?)
- 五、两阶段提交
-
- [5.1 为什么需要两阶段提交?](#5.1 为什么需要两阶段提交?)
- [5.2 两阶段提交流程](#5.2 两阶段提交流程)
- [5.3 崩溃恢复规则](#5.3 崩溃恢复规则)
- [5.4 图解](#5.4 图解)
- 六、崩溃恢复实战
-
- [6.1 模拟崩溃](#6.1 模拟崩溃)
- [6.2 查看恢复日志](#6.2 查看恢复日志)
- 七、备份与恢复实战
-
- [7.1 备份类型](#7.1 备份类型)
- [7.2 mysqldump 全量备份](#7.2 mysqldump 全量备份)
- [7.3 恢复](#7.3 恢复)
- [7.4 Binlog 增量恢复](#7.4 Binlog 增量恢复)
- [7.5 PITR 时间点恢复](#7.5 PITR 时间点恢复)
- 八、实战任务
- 九、本篇小结
一、InnoDB 整体架构
1.1 InnoDB 内存 + 磁盘结构
┌─────────────────────────────────────────────────────────┐
│ InnoDB 内存结构 │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Buffer Pool(缓冲池) │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │数据页 │ │索引页 │ │undo页│ │插入缓冲│ │ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Change Buffer │ │ Adaptive Hash│ │ Log Buffer │ │
│ │ (写缓冲) │ │ (自适应哈希)│ │ (日志缓冲)│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────┐
│ InnoDB 磁盘结构 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 表空间 │ │ Redo Log │ │ Undo Log │ │ Binlog │ │
│ │ .ibd │ │ ib_logfile│ │ undo_001 │ │ binlog. │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
1.2 各组件作用
| 组件 | 作用 | 特点 |
|---|---|---|
| Buffer Pool | 缓存数据页和索引页 | 最重要,越大越好 |
| Change Buffer | 缓存非唯一索引的写操作 | 减少随机 IO |
| Adaptive Hash | 自适应哈希索引 | 加速等值查询 |
| Log Buffer | 缓存 redo log | 定期刷盘 |
| Redo Log | 崩溃恢复 | 循环写 |
| Undo Log | 回滚 + MVCC | 版本链 |
| Binlog | 主从复制 + 恢复 | 追加写 |
二、Buffer Pool 缓冲池
2.1 为什么需要 Buffer Pool?
磁盘 IO 慢:
内存访问:100ns
SSD 随机读:100μs(慢 1000 倍)
机械硬盘:10ms(慢 10 万倍)
Buffer Pool 把热数据放内存,避免每次都读磁盘。
2.2 Buffer Pool 工作流程
查询请求
│
↓
Buffer Pool 中有?
│
├── 有 → 直接返回(内存读)
│
└── 没有 → 从磁盘读入 Buffer Pool
│
↓
返回数据
│
↓
按 LRU 淘汰旧页
2.3 LRU 改进版
普通 LRU 问题:
全表扫描会把热数据挤出去
InnoDB 改进 LRU:
┌─────────────────────────────────────┐
│ 冷数据区(37%) │ 热数据区(63%) │
└─────────────────────────────────────┘
↑ ↑
新页插入这里 频繁访问的页
规则:
① 新页插入冷数据区头部
② 冷数据区页被访问,且停留超过 1 秒,移到热数据区
③ 热数据区页被访问,移到热数据区头部
2.4 查看 Buffer Pool 状态
sql
SHOW ENGINE INNODB STATUS\G
关键指标:
Buffer pool hit rate 1000 / 1000 ← 命中率,越接近 100% 越好
或用:
sql
SELECT
VARIABLE_NAME,
VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME IN (
'Innodb_buffer_pool_read_requests',
'Innodb_buffer_pool_reads'
);
命中率 = 1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests
三、三大日志:Redo / Undo / Binlog
3.1 日志总览
┌──────────────┬──────────────┬──────────────┐
│ Redo Log │ Undo Log │ Binlog │
├──────────────┼──────────────┼──────────────┤
│ InnoDB 独有 │ InnoDB 独有 │ Server 层 │
│ 物理日志 │ 逻辑日志 │ 逻辑日志 │
│ 循环写 │ 版本链 │ 追加写 │
│ 崩溃恢复 │ 回滚 + MVCC │ 主从 + 恢复 │
└──────────────┴──────────────┴──────────────┘
3.2 Redo Log
作用:崩溃恢复,保证持久性。
写流程:
① 修改数据 → 写 redo log buffer
② 事务提交 → redo log 刷盘(innodb_flush_log_at_trx_commit=1)
③ 后台线程慢慢刷数据页到磁盘
为什么快?
顺序写 redo log 比随机写数据页快得多
循环写:
┌─────────────────────────────────────┐
│ ib_logfile0 │ ib_logfile1 │
│ write pos ──→ │
│ ←── checkpoint │
└─────────────────────────────────────┘
write pos:当前写位置
checkpoint:已刷盘位置
两者之间是空闲区域
关键参数:
sql
-- 0:每秒刷一次(可能丢1秒)
-- 1:每次提交都刷(最安全,默认)
-- 2:每次提交写 OS 缓存,每秒刷盘
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
3.3 Undo Log
作用:回滚 + MVCC 版本链。
事务修改数据时:
① 把旧值写入 undo log
② 修改当前行
回滚时:
用 undo log 恢复旧值
MVCC 读时:
沿 undo log 版本链找到可见版本
类型:
| 类型 | 作用 |
|---|---|
| insert undo | 回滚插入 |
| update undo | 回滚更新 + MVCC |
3.4 Binlog
作用:主从复制 + 数据恢复。
三种格式:
| 格式 | 内容 | 优点 | 缺点 |
|---|---|---|---|
| STATEMENT | SQL 语句 | 日志小 | 函数可能导致不一致 |
| ROW | 行变更 | 精确 | 日志大 |
| MIXED | 混合 | 兼顾 | 复杂 |
查看 binlog:
sql
SHOW VARIABLES LIKE 'log_bin';
SHOW BINARY LOGS;
SHOW BINLOG EVENTS IN 'binlog.000001' LIMIT 10;
用 mysqlbinlog 解析:
bash
mysqlbinlog --base64-output=DECODE-ROWS -v binlog.000001
3.5 三日志对比
Redo Log Undo Log Binlog
──────── ──────── ──────
所属层 InnoDB InnoDB Server
日志类型 物理 逻辑 逻辑
写入方式 循环写 随机写 追加写
主要作用 崩溃恢复 回滚+MVCC 主从+恢复
是否可关闭 不可 不可 可
四、WAL 机制:为什么写日志比写数据快?
4.1 WAL 原理
WAL(Write-Ahead Logging):
先写日志,再写数据
传统方式:
修改数据 → 随机写磁盘数据页 → 慢
WAL 方式:
修改数据 → 顺序写 redo log → 快
→ 后台慢慢刷数据页
4.2 图解
事务提交
│
↓
① 写 redo log(顺序写,快)
│
↓
② 返回提交成功
│
↓
③ 后台线程异步刷数据页(随机写,慢)
4.3 为什么快?
顺序写:磁头连续移动,速度快
随机写:磁头频繁寻道,速度慢
redo log 顺序写 ≈ 内存速度
数据页随机写 ≈ 磁盘速度
五、两阶段提交
5.1 为什么需要两阶段提交?
如果先写 redo log,再写 binlog:
redo log 写完,binlog 没写 → 崩溃
→ 主库有数据,从库没同步 → 不一致
如果先写 binlog,再写 redo log:
binlog 写完,redo log 没写 → 崩溃
→ 从库有数据,主库没有 → 不一致
5.2 两阶段提交流程
事务提交
│
↓
① 写 redo log,状态 = prepare
│
↓
② 写 binlog
│
↓
③ 写 redo log,状态 = commit
5.3 崩溃恢复规则
崩溃后重启
│
├── redo log 是 prepare,binlog 完整
│ → 提交事务
│
├── redo log 是 prepare,binlog 不完整
│ → 回滚事务
│
└── redo log 是 commit
→ 提交事务
5.4 图解
redo log binlog
──────── ──────
① prepare
② write
③ commit
崩溃点分析:
①后崩溃 → 回滚
②后崩溃 → 检查 binlog 是否完整,完整则提交
③后崩溃 → 已提交
六、崩溃恢复实战
6.1 模拟崩溃
bash
# 启动 MySQL 容器
docker start mysql8
# 插入数据
docker exec -it mysql8 mysql -uroot -proot123 -e "
USE shop;
INSERT INTO users (username, phone) VALUES ('崩溃测试', '13900000001');
SELECT * FROM users WHERE phone = '13900000001';
"
# 强制杀掉 MySQL(模拟崩溃)
docker kill mysql8
# 重启
docker start mysql8
# 验证数据是否还在
docker exec -it mysql8 mysql -uroot -proot123 -e "
USE shop;
SELECT * FROM users WHERE phone = '13900000001';
"
预期结果:数据还在,因为 redo log 保证了持久性。
6.2 查看恢复日志
bash
docker logs mysql8 | grep -i recover
七、备份与恢复实战
7.1 备份类型
备份
│
├── 逻辑备份
│ ├── mysqldump:导出 SQL
│ └── mysqlpump:并行导出
│
├── 物理备份
│ ├── XtraBackup:热备
│ └── cp 数据文件:冷备
│
└── 按范围
├── 全量备份
├── 增量备份
└── 差异备份
7.2 mysqldump 全量备份
bash
# 备份整个 shop 库
docker exec mysql8 mysqldump -uroot -proot123 \
--single-transaction \
--routines \
--triggers \
--events \
shop > /tmp/shop_backup.sql
# 备份所有库
docker exec mysql8 mysqldump -uroot -proot123 \
--all-databases \
--single-transaction > /tmp/all_backup.sql
| 参数 | 作用 |
|---|---|
| --single-transaction | 一致性快照,不锁表 |
| --routines | 包含存储过程 |
| --triggers | 包含触发器 |
| --events | 包含事件 |
7.3 恢复
bash
# 恢复 shop 库
docker exec -i mysql8 mysql -uroot -proot123 shop < /tmp/shop_backup.sql
7.4 Binlog 增量恢复
bash
# 查看当前 binlog
docker exec mysql8 mysql -uroot -proot123 -e "SHOW BINARY LOGS;"
# 模拟误删
docker exec mysql8 mysql -uroot -proot123 -e "
USE shop;
DELETE FROM users WHERE phone = '13900000001';
"
# 用 binlog 恢复到删除前
docker exec mysql8 mysqlbinlog \
--start-datetime="2024-01-01 00:00:00" \
--stop-datetime="2024-01-01 12:00:00" \
/var/lib/mysql/binlog.000001 | \
docker exec -i mysql8 mysql -uroot -proot123
7.5 PITR 时间点恢复
PITR(Point-In-Time Recovery)流程:
① 恢复最近的全量备份
↓
② 用 binlog 重放从备份点到目标时间的所有变更
↓
③ 数据恢复到目标时间点
bash
# 完整流程
# 1. 恢复全量备份
docker exec -i mysql8 mysql -uroot -proot123 shop < /tmp/shop_backup.sql
# 2. 重放 binlog 到误删前
docker exec mysql8 mysqlbinlog \
--start-datetime="2024-01-01 02:00:00" \
--stop-datetime="2024-01-01 10:00:00" \
/var/lib/mysql/binlog.000001 | \
docker exec -i mysql8 mysql -uroot -proot123
八、实战任务
任务清单
- 查看 Buffer Pool 命中率
- 查看 redo log 配置
- 查看 binlog 格式
- 模拟崩溃恢复
- 用 mysqldump 全量备份
- 恢复备份
- 用 binlog 做增量恢复
- 完成一次 PITR
自检问题
- Buffer Pool 为什么用改进版 LRU?
- Redo Log 和 Binlog 的区别?
- WAL 为什么能提升性能?
- 两阶段提交解决什么问题?
- 崩溃后怎么判断事务该提交还是回滚?
- mysqldump 的 --single-transaction 有什么用?
- PITR 的流程是什么?
九、本篇小结
第6篇 核心收获
│
├── InnoDB 架构
│ ├── 内存:Buffer Pool / Change Buffer / Log Buffer
│ └── 磁盘:表空间 / Redo / Undo / Binlog
│
├── Buffer Pool
│ ├── 缓存数据页
│ ├── 改进 LRU:冷热分区
│ └── 命中率越接近 100% 越好
│
├── 三大日志
│ ├── Redo Log:物理日志,崩溃恢复
│ ├── Undo Log:逻辑日志,回滚 + MVCC
│ └── Binlog:逻辑日志,主从 + 恢复
│
├── WAL
│ ├── 先写日志再写数据
│ └── 顺序写比随机写快
│
├── 两阶段提交
│ ├── prepare redo → write binlog → commit redo
│ └── 保证 redo 和 binlog 一致
│
└── 备份恢复
├── mysqldump 全量备份
├── binlog 增量恢复
└── PITR 时间点恢复
下一篇:第7篇《高级特性实战:视图、存储过程、触发器、分区、全文索引》