两本日志,两个主人
第 7 篇讲过 redo log,但 InnoDB 里还有另一本日志 binlog,两人分工不同:
| 维度 | redo log | binlog |
|---|---|---|
| 所属层 | InnoDB 引擎层 | MySQL Server 层(所有引擎都有) |
| 内容 | 物理日志:某页做了什么修改 | 逻辑日志:语句或行变更 |
| 写法 | 固定大小循环写 | 追加写,写满切换新文件 |
| 用途 | 崩溃恢复 | 主从复制、时间点恢复 |
binlog 的三种格式
| 格式 | 记录什么 | 优劣 |
|---|---|---|
| statement | 原始 SQL 语句 | 省空间;但 NOW()、UUID()、LIMIT 这类不确定执行的内容会让主从各算各的,数据不一致 |
| row(默认) | 每行数据的变更前后镜像 | 最可靠,从库回放结果确定;一条大 update 会产生海量事件 |
| mixed | 多数用 statement,检测到不确定内容切 row | 折中;row 已是默认,mixed 存在感降低 |
第 13 篇讲读写分离和第 21 篇讲恢复时都会用到 binlog,row 格式是主从安全性的基石。
问题:两本日志能不能各写各的
假设提交时先写完 redo 再写 binlog,中间崩溃:重启后 redo 重放,主库数据是新的,但 binlog 没写——从库永远拿不到这条修改,主从不一致。反过来先 binlog 后 redo,崩溃后主库丢了这条数据,从库却有一条主库不存在的记录。只要顺序执行就可能断在中间,需要的是「要么都成、要么都不算」的原子协议——这就是两阶段提交。
两阶段提交的时序
- 阶段一(prepare):redo log 写入并 fsync,标记为 prepare 状态;
- 阶段二(commit):binlog 写入并 fsync,然后把 redo log 标记为 commit 状态。
崩溃恢复时的判定规则只有一条:redo 处于 prepare,就去查对应的 binlog——binlog 完整就提交,不完整就回滚。binlog 的事件有内部校验,写完整才算数。于是两个方向都对:redo 成 binlog 没成 → 回滚,主从都没有;binlog 成了 → 提交,主从都有。binlog 成了没提交的,恢复时由它来补票。
顺带解开第 9 篇结尾的悬念:主从复制的本质就是从库按 binlog 重放,而两阶段提交保证的「binlog 与主库数据一致」,正是主从一致性的前提。
双 1 配置与组提交
# 事务安全的最高配置(默认值)
innodb_flush_log_at_trx_commit = 1 # redo 每次提交 fsync
sync_binlog = 1 # binlog 每次提交 fsync两个 1 同时在线,才算真正的 crash-safe:最多丢一个事务都不行。代价是每次提交两次 fsync——机械盘时代这是重负,如今 SSD 上配合组提交(group commit)(多个并发事务的 fsync 合并成一次)扛住高并发完全没问题。报表库、日志归档库等能容忍极端情况丢 1 秒的场景,可以放成 2 和 100 这类组合换性能,交易链路别动。
本篇划重点
- redo 物理循环写管崩溃恢复,binlog 逻辑追加写管复制与恢复;
- 两阶段提交 = redo prepare → binlog → redo commit,恢复时以 binlog 完整性定生死;
- 双 1 是交易链路的底线,组提交让双 1 不再是性能包袱。
日志的地基全部打完,从下一篇起进入高可用三部曲:先从零动手搭一主两从——my.cnf 参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO,一条不落。
咖啡凉了,记得趁热喝。
评论 (0)