Mysql日志架构
redo log
redo log记录的是物理变更,某个数据页在什么位置做了什么修改,用于崩溃恢复,修复未刷盘的已提交数据。
设计背景 or 主要用途:
1)避免数据每次更新立刻刷盘带来的IO开销。redo log是预写日志WAL技术的实现,先写日志,再写磁盘,先顺序写到redo log,最后根据不同的刷盘策略进行刷盘。
2)保证事务的持久性,数据库宕机后通过重做日志将已提交事务恢复到磁盘。
redo log,数据页、与事务
在事务执行阶段,InnoDB先将生成的redo log顺序记录到内存日志缓冲redo log buffer中,再把数据改动先写到内存的Buffer Pool; redo log --> redo log buffer->os page cache ->ib_logfile 数据页:buffer_pool--> double_write buffer--> os page cache---> ibd file
在事务提交阶段,InnoDB根据innodb_flush_log_at_trx_commit参数的策略将redo log从内存缓冲区按序、大块的持久化到ib_logfile文件中,而数据页(脏页)由后台线程批量、尽可能的按顺序(将相同页、同表空间聚集到一起)持久化到磁盘ibd文件中
双写缓冲区: 为了避免极端情况如断电,存储故障导致数据页写了一半崩溃导致只有“半页写入” 导致新旧数据混在一起。这种情况下即便有redo log也无法恢复数据,因为底层存储/文件系统在“写一大块数据”这件事上并不能保证原子性,只能以更小的扇区(sector)或块(block)为单位做原子写入,而 InnoDB 的页大小(通常 16 KB)往往跨越了多个扇区/块,所以只能在上层软件里做两阶段写入的保障
innodb_flush_log_at_trx_commit刷盘行为:
| 设置值 | 缓冲区和刷盘行为 | 刷新到磁盘 | 崩溃恢复能力 | 数据丢失风险 |
|---|---|---|---|---|
0 | 提交事务时只写入 redo log 缓冲区(内存)➔ 写入 OS 缓存(page cache),不写入日志文件;后台线程每秒将 OS 缓存中的日志内容写入日志文件并 fsync 刷盘。 | 每秒一次 | 低 | 可能丢失最近 1 秒内的所有事务数据(日志仅在 OS 缓存或缓冲区中)。 |
1(默认) | 提交事务时写入 redo log 缓冲区➔ 写入 OS 缓存 并立即调用 fsync() 将日志文件刷到磁盘;确保 redo log 文件和 OS 缓存都已持久化。 | 每次提交 | 高 | 最小,已提交的事务不会丢失(日志已刷入 OS 缓存并持久化到磁盘)。 |
2 | 提交事务时写入 redo log 缓冲区➔ 写入 OS 缓存(page cache),但不立即 fsync;后台线程每秒对日志文件调用 fsync,将 OS 缓存中的日志刷到磁盘。 | 每秒一次 | 中 | 可能丢失最近 1 秒内的事务数据(日志在 OS 缓存中但未持久化到磁盘)。 |
redo log文件格式
innoDB 的 redo log 由一组固定大小的文件组成(旧版 ib_logfile0/1,8.0+ 默认 32 个 #ib_redo 文件),逻辑上首尾相连成环。
有两个指针写指针和检查指针,一边写新日志,一边擦已落盘的日志。
写入时靠递增的write_LSN做写指针,指向当前的写入位置,一边写一边往后移,写到组尾后自动绕回组头,保证日志空间可以循环利用。
写入时为防止未刷盘日志被覆盖引入检查点指针checkpoint_LSN,指向当前要擦除的值,一边擦一边往后移(由后台线程不断将buffer pool中的脏页写进ibd文件并且推进)。小于checkpoint_lsn的日志代表已经刷盘,write指针可以覆盖.从而保证崩溃恢复所需的日志永不丢失。
如果写指针追上了检查指针,代表没有空间可以写入,阻塞,需要后台线程将检查指针往前推进一下才可以写入。
redo log如何实现崩溃恢复
- 启动的时候innodb检测到非正常关闭,进入crash_recovery流程
- 分析阶段扫描redo log 最新的checkpoint开始 重建事务表和脏页表
- 重做阶段重做所有已提交但未刷盘的更改
- 回退阶段回滚所有未提交的事务
- crash_recovery恢复完成后才server才开始接收新连接
redo log主要系统变量:
| 参数名 | 作用/描述 | 常见生产设置/建议 |
|---|---|---|
innodb_log_file_size | 单个 redo log 文件大小。决定每个日志文件的容量。 | 通常设置为 512MB ~ 4GB,大事务/高并发系统可设更大,避免频繁 checkpoint。 |
innodb_log_files_in_group | redo log 文件组的数量(循环使用)。 | 通常为 2,MySQL 8.0 默认 2;极端场景可增加以优化 IO 性能。 |
innodb_flush_log_at_trx_commit | 事务提交时日志的刷盘策略,决定事务持久化安全性。 | 推荐设为 1(最安全);性能敏感且可接受少量数据丢失时设为 2。 |
innodb_log_buffer_size | redo log 缓冲区大小,减少频繁刷盘。 | 通常设为 16MB~64MB,大事务多时设大些(如128MB) 。 |
innodb_flush_log_at_timeout | 异步刷新日志文件的时间间隔(秒),影响 innodb_flush_log_at_trx_commit=0/2。 | 默认为 1 秒;大多数场景下维持默认即可,需精细调优时可调整。 |
Undo log
存储行数据的历史版本,存储在系统表空间中。用于事务回滚,实现MVCC快照读,崩溃恢复
设计背景 or 主要用途
-
事务原子性与回滚
- 在事务执行过程中,若后续操作失败或客户端发起回滚,必须能够恢复到操作前的一致状态。
- Undo log 记录了“修改前的旧值”,一旦回滚,就根据这些日志一步步反向应用(undo),保证事务的原子性。
-
MVCC 快照读
- 多版本并发控制(MVCC)需要在并发读写时,为每个事务提供一个一致性的快照视图。
- 通过保留历史版本的 undo log,读取事务可以访问“回滚前的旧版本”,实现无锁读且不阻塞写。
-
崩溃恢复中的回滚
- 在数据库意外宕机后,重启时除了要“重做”已提交的 redo log,还要“回滚”那些未提交的事务,这时也会用到 undo log。
undo log与事务:
-
INSERT:给新插入的行生成一个 undo 记录,内容是“删除此行”。
-
UPDATE:给被修改行生成 undo 记录,内容是“将此行恢复为旧值”。
-
DELETE:给被删除行生成 undo 记录,内容是“重新插入此行的旧数据”。
在事务执行 DML 操作时,修改缓冲池中的页之前立即写入 undo log buffer,由后台线程或在事务提交/回滚时刷新到表空间中。undo log的刷盘策略与redo log也由innodb_flush_log_at_trx_commit控制
innodb_flush_log_at_trx_commit行为:
| 设置值 | 缓冲区和刷盘行为 | 刷新到磁盘 | 崩溃恢复能力 | 数据丢失风险 |
|---|---|---|---|---|
0 | 事务提交时只写入 undo log buffer(内存)➔ 写入 OS page cache,不写入 undo tablespace 文件;后台 page_cleaner/master 线程每秒将 OS 缓存中的 undo log 页写入 undo tablespace 并 fsync 刷盘。 | 每秒一次(后台) | 低 | 可能丢失最近 1 秒内的 undo 日志,回滚或 MVCC 读快照可能找不到旧版本。 |
1(默认) | 事务提交时写入 undo log buffer➔ 写入 OS page cache 并立即调用 fsync() 将对应 undo tablespace 文件页刷到磁盘;保证 undo log 和 OS 缓存都已持久化。 | 每次提交 | 高 | 极低,已提交事务的 undo 记录不会丢失,MVCC 和回滚都能正常工作。 |
2 | 事务提交时写入 undo log buffer➔ 写入 OS page cache,但不立即 fsync;后台 page_cleaner/master 线程每秒对 undo tablespace 文件调用 fsync,将 OS 缓存中的 undo log 刷盘。 | 每秒一次(后台) | 中 | 可能丢失最近 1 秒内的 undo 日志,最坏情况下无法回滚或构造快照。 |
事务提交以后:
- 如果是普通事务DML undo log不能马上清楚,需要提供mvcc快照读
- 如果是DDL,即时生效,undo log事务提交后马上清理。
清理逻辑:无任何活跃事务访问旧版本数据并且purge线程(异步)检测undo log无用
binlog
binlog主要记录 执行了什么SQL语句,用于主从复制和数据恢复(闪回),可重放到其他实例
设计背景 or 主要用途:
-
主从复制
- MySQL 需要将主库的变更实时同步到从库,传统物理文件级复制耦合度高、效率低。
- Binlog 以逻辑事件形式记录所有 DML/DDL 操作,从库只需按顺序重放,即可高效保持数据一致。
-
增量备份与 Point-In-Time Recovery (PITR)
- 物理备份只能恢复到备份那一刻,无法补偿其后产生的变更。
- 结合全量备份 + 后续 binlog,能够将恢复点精确到任意时刻或事务,满足业务连续性要求。
-
审计与审查:Binlog 记录了完整的语句或行级变更,可用于操作审计、分析用户行为,或在测试环境回放生产流量。
-
解耦存储与逻辑:将持久化的物理存储机制(redo log + 数据页)和逻辑变更流(binlog)分离,让存储引擎和复制机制各司其职、灵活扩展。
binlog与事务: 当事务提交后,更改事件会被送到server层的binlog子系统,将事件序列化为二进制写入日志文件。
日志文件: 文件名格式是 <base_name>.00000n,base_name 由 --log-bin[=base_name](或系统变量 log_bin_basename)指定,后缀从 .000001 起依次递增
索引文件:同时会维护一个 <base_name>.index 文件,记录所有已启用的 binlog 文件名,以便 Server 启动或复制时加载
当binlog.xxx文件到达max_binlog_size(默认1G)或者手动执行flush logs, server会在索引文件中记录并切换到下一个编号的文件
binlog刷盘逻辑
依赖sync_binlog控制刷盘行为
| 设置值 | 缓冲区和刷盘行为 | 崩溃风险 | 性能 | 适用场景 |
|---|---|---|---|---|
0 | 提交事务时将 binlog 写入 binlog 缓冲区 ➔ 写入 OS 缓存(page cache),不调用 fsync() ,完全依赖操作系统自行决定何时刷到磁盘。 | 服务器/系统崩溃时,可能丢失最近几秒的 binlog 数据。 | 性能最高 | 对数据可靠性要求不高、追求极致性能的场景(如日志分析等)。 |
1(推荐值) | 提交事务时将 binlog 写入 binlog 缓冲区 ➔ 写入 OS 缓存,并立刻调用 fsync() 强制刷盘,确保事务提交后 binlog 已持久化到磁盘。 | 最安全,事务提交后即使系统崩溃也能恢复 binlog。 | 性能最差 | 高可靠性要求场景,如主从复制、金融/资金类系统等。 |
N (>1) | 提交事务时将 binlog 写入 binlog 缓冲区 ➔ 写入 OS 缓存,累计到 N 次事务提交后再批量执行一次 fsync() 刷盘。 | 崩溃时可能丢失最近 N 次事务的 binlog 数据。 | 介于 0 和 1 之间 | 一般业务,允许在可靠性和性能间权衡的场景(如中低频电商等)。 |
binlog主要的系统变量
| 变量 | 含义 | 默认值 | 调优意义 |
|---|---|---|---|
log_bin | 是否开启二进制日志 | ON | 复制/增量恢复必须开启 |
log_bin_basename | binlog 文件基名 / 路径 | “binlog” | 指定写入目录或前缀 |
max_binlog_size | 单个 binlog 文件最大大小 | 1 GB | 控制切换频率,防止单文件过大 |
sync_binlog | 每写 N 次事务后 fsync binlog | 1 | =1 最安全;=0/>1 提升性能但有数据风险 |
三个日志在更新流程中的顺序
| 步骤 | 操作 | 写入目标 | 哪个线程/模块 | 备注 |
|---|---|---|---|---|
| 1 | 客户端发起 DML | — | SQL 接入线程 | |
| 2 | 生成 Undo log | Undo log buffer(内存)→ undo tablespace | InnoDB trx 模块 | 记录旧值,用于回滚和 MVCC;redo 之前必须先有 undo |
| 3 | 生成 Redo log | Redo log buffer(内存) | InnoDB log_sys 模块 | 物理日志,按 LSN 顺序追加 |
| 4 | 修改 Buffer Pool | Buffer Pool(脏页) | InnoDB buf_pool 模块 | 真正的内存页改动,可见给并发事务 |
| 5 | 写 Binlog(write) | Binlog buffer → OS 缓存 | Server binlog 子系统 | 将逻辑事件写到 OS page cache |
| 6 | 刷新 Binlog(fsync) | OS 缓存 → binlog 文件 | Server binlog 子系统 | 由 sync_binlog 决定是否每次都 fsync |
| 7 | 刷新 Redo log(fsync) | Redo log buffer → OS 缓存 → ib_logfile* | InnoDB log writer | 由 innodb_flush_log_at_trx_commit 控制;组提交可合并多事务 fsync |
| 8 | Commit 返回 | — | — | 确保 binlog+redo 均已持久 |
| 9 | 双写缓冲 & 刷脏页 | Buffer Pool 脏页 → doublewrite → OS cache → .ibd | InnoDB page cleaner | 异步后台,先写 doublewrite 再写表空间,防“半页写入” |
Q&A
为什么有了redo log还需要binlog
只有redo log只能通过物理备份恢复到某一刻,主要用作崩溃恢复, 无法做到备份后再增量恢复 并且分布式架构通常需要主库改动实时传到slave。
binlog提供的逻辑变更流解决了
- 复制同步:slave不需要读主库的数据文件,而是重放主库的逻辑事件即可
- 备份+binlog 将数据恢复到秒级或者事务级,不再局限全量快照时间点
为什么bin/redo/undo 日志都存在先写进程内存->OS缓存->物理磁盘,而不是直接写盘?
| 缓冲层次 | 概念/位置 | 写入方式 | 作用 |
|---|---|---|---|
| 第一级 | InnoDB进程内存缓冲区log buffer | 应用调用 write() | 聚合多条日志,减少内核调用次数,MySQL 能根据 innodb_flush_log_at_trx_commit/sync_binlog 等参数灵活管理刷盘策略,而不单纯依赖 OS 的缓冲逻辑 |
| 第二级 | 操作系统页缓存(Page Cache) | write() 到内核缓存 | 1. 利用内核合并小写入为大 I/O。2. 内核异步调度真正的磁盘写入 |
两级缓冲的分层架构把日志写入拆成:先在进程内存聚合,减少系统调用;再写入 OS 缓存利用页缓存合并 I/O;最后由 fsync 控制何时落盘。这样既保证了日志边界完整,也最大化吞吐,同时还能通过配置折中性能与可靠性
参考
《高性能MySQL-第四版》
《极客时间-MySQL45讲》
《MySQL技术内幕-InnoDB存储引擎-第二版》