【数据库】MySQL 实战精通系列 · 第6篇:InnoDB 存储引擎、日志与崩溃恢复

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

自检问题

  1. Buffer Pool 为什么用改进版 LRU?
  2. Redo Log 和 Binlog 的区别?
  3. WAL 为什么能提升性能?
  4. 两阶段提交解决什么问题?
  5. 崩溃后怎么判断事务该提交还是回滚?
  6. mysqldump 的 --single-transaction 有什么用?
  7. 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篇《高级特性实战:视图、存储过程、触发器、分区、全文索引》

相关推荐
杨云龙UP2 小时前
TDengine 3.4.2.8 Community 三节点三副本生产集群部署实战(DNode/MNode/taosAdapter/Explorer)
大数据·linux·运维·数据库·tdengine·时序库
梦帮科技2 小时前
领域认知知识库图谱注入:从双式记账图网络到高质量问答对自动化合成流水线
运维·网络·数据库·人工智能·矩阵·架构·自动化
Long long ago.2 小时前
vastbase数据库运行sql宕机重启解决
数据库·sql
SelectDB技术团队3 小时前
一条日志两套引擎的账:把 Elasticsearch 检索与分析合并到同一份数据的落地写法
大数据·数据库·elasticsearch·搜索引擎·全文检索·日志·apache doris
数据库百宝箱3 小时前
rum&gin索引对比
java·数据库·gin
wefg13 小时前
【Redis】初识 Redis
数据库·redis·缓存
FYKJ_20104 小时前
django电影推荐系统55066-计算机课程设计、毕业设计
vue.js·spring boot·python·mysql·typescript·spark·django
hacker_LeeFei6 小时前
Windows 下配置 Redis 8.10.2 开机自启:一次踩坑全记录
数据库·windows·redis
这个DBA有点耶6 小时前
MySQL字符集与排序规则深入:索引失效的隐蔽场景与排查方法
数据库·mysql·代码规范