日志与持久化篇 —— 数据的生命线
摘要
如果说 Buffer Pool 是 MySQL 的短期记忆,那么日志(Logs)就是它的长期记忆。
当服务器突然断电,内存里的脏页瞬间蒸发,数据库凭什么保证数据不丢?
本章我们将揭开 WAL 的神秘面纱,解析 LSN 如何记录时间的流逝,并解释为什么 Redo Log 必须和 Double Write Buffer 互为唇齿。
5.1 核心哲学:WAL (Write-Ahead Logging)
1. 💀 现场:性能与安全的悖论
-
矛盾:
-
如果每次
UPDATE都把 16KB 的数据页刷入磁盘,那是随机写(Random Write),性能极差(IOPS 瓶颈)。 -
如果不刷盘,放在内存里,断电就丢数据。
-
-
解法:
-
先把“修改操作”记录下来,追加写到一个日志文件里(顺序写,极快)。
-
告诉用户“操作成功”。
-
后台线程慢慢把内存里的脏页刷回磁盘。
-
-
结论:WAL 的本质,就是利用磁盘的顺序写性能来模拟内存的写性能,同时保证持久性。
5.2 InnoDB 的物理记忆:Redo Log
1. 物理结构:循环写的贪吃蛇
Redo Log 是 InnoDB 引擎层 的私有日志,记录的是物理修改(例如:“在第 10 号表空间的第 50 页偏移量 200 处,把值从 A 改为 B”)。
-
ib_logfile:固定大小的文件组(循环写)。
-
write pos:当前的写入点(蛇头)。
-
checkpoint:当前的擦除点(蛇尾)。这之前的数据脏页已经安全落盘,可以覆盖。
-
追尾危机:当
write pos追上checkpoint,MySQL 必须停止更新,疯狂推进刷脏页(Page Flush)。这就是数据库偶尔突然卡顿(Jitter)的元凶之一。
2. 核心心脏:LSN (Log Sequence Number)
InnoDB 内部也是有“时间”的,这个时间就是 LSN。它是一个单调递增的 8 字节数字。
它出现在三个关键位置,通过比对它们,InnoDB 判断系统是否健康:
-
LSN in Log:Redo Log 写到哪里了?(最新进度)。
-
LSN in Buffer Pool:内存脏页的进度(通常 < Log LSN)。
-
LSN in Disk (Checkpoint):磁盘数据页的进度(最旧)。
Crash Recovery 逻辑:
启动时,读取磁盘页面的 LSN。如果 Disk LSN < Redo Log LSN,说明掉电时有数据没刷盘,需要重放 Redo Log 进行恢复。
3. 深度辨析:为什么有了 Redo Log 还需要 Double Write?
这是 Ch 2 和 Ch 5 的逻辑闭环。
-
问题:Redo Log 记录的是“对页的修改”。如果断电时,正在写的数据页被写坏了(16KB 只写了 4KB,发生了 Page Tearing),由于页面本身的 Checksum 都不对了,InnoDB 认为这是个垃圾页。
-
后果:Redo Log 无法在一个“垃圾页”上应用修改。
-
解法:
-
Step 1: 恢复前,先检查页的 Checksum。
-
Step 2: 如果页坏了,从 Double Write Buffer(共享表空间里的副本)把完好的旧页还原回来。
-
Step 3: 页面修好后,再应用 Redo Log。
-
-
结论:Double Write 保证页面的物理完整性,Redo Log 保证页面的逻辑最新性。
5.3 MySQL 的逻辑记忆:Binlog
1. 为什么需要两份日志?
-
Redo Log:InnoDB 的。循环写,无法保留历史,只用于崩溃恢复。
-
Binlog:Server 层的。追加写,全量保留。它是 MySQL 生态的基石。
2. Binlog 的三大形态
格式
内容
优点
缺点
Statement
SQL 语句
日志量小
UUID(), NOW() 可能导致主从不一致
Row (推荐)
每一行的变化
绝对一致,支持 CDC
批量 UPDATE 时日志量大
Mixed
混合模式
折中
复杂度高
3. 架构师视角:Binlog 也是数据源
在现代架构中,Binlog 不仅仅用于主从复制,它还是 CDC (Change Data Capture) 的源头。
-
工具:Canal / Debezium。
-
链路:
MySQL Binlog -> Canal -> Kafka -> Hadoop/ES。 -
这也是为什么我们强调严禁在生产环境随便清空 Binlog。
5.4 生死协作:两阶段提交 (2PC)
Redo Log 和 Binlog 是两个独立的系统,如何保证它们的一致性?
1. 2PC 流程解析
执行 UPDATE 时:
-
Prepare 阶段:InnoDB 写 Redo Log,标记为
PREPARE。 -
Binlog 阶段:Server 层写 Binlog,并刷盘(fsync)。
-
Commit 阶段:InnoDB 将 Redo Log 标记为
COMMIT。
2. 崩溃恢复的“判决书”
如果数据库在中间挂了,重启时怎么判断?
-
情况 A:Redo Log 是
COMMIT\to 直接提交。 -
情况 B:Redo Log 是
PREPARE\to 拿着 XID 去查 Binlog。-
如果 Binlog 里有完整记录 \to 提交(说明 Binlog 已经落盘,为了保证主从一致,必须提交)。
-
如果 Binlog 里没有记录 \to 回滚(说明 Binlog 没写完,必须回滚)。
-
5.5 实战:工具与配置
1. 显微镜:mysqlbinlog
不要只听概念,要亲眼看。
Bash
# 查看 Binlog 内容,解码 Base64 也就是 Row 格式
mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000001
- 场景:误删了数据,需要恢复。可以通过
mysqlbinlog找到DELETE发生的时间点,将前面的日志导出为 SQL 重新执行(Point-in-Time Recovery)。
2. 性能与安全的权衡:双 1 配置
-
innodb_flush_log_at_trx_commit = 1:每次事务都刷 Redo Log。 -
sync_binlog = 1:每次事务都刷 Binlog。
调优建议:
-
核心支付库:必须双 1。
-
日志库/统计库:改为
2和0。性能可提升 5-10 倍,代价是 Crash 可能丢 1 秒数据。
3. 组提交 (Group Commit)
MySQL 为了解决“双 1”带来的 IOPS 瓶颈,引入了组提交。
-
原理:拖延战术。当第一个事务要刷盘时,等几微秒,凑齐一批事务,一次 fsync 刷进去。
-
效果:大幅降低 IOPS 压力,提高吞吐量。