MySQL-三大日志

159 阅读12分钟

Mysql日志架构

image.png

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文件格式

image.png

innoDB 的 redo log 由一组固定大小的文件组成(旧版 ib_logfile0/1,8.0+ 默认 32 个 #ib_redo 文件),逻辑上首尾相连成环。

有两个指针写指针和检查指针,一边写新日志,一边擦已落盘的日志。

写入时靠递增的write_LSN做写指针,指向当前的写入位置,一边写一边往后移,写到组尾后自动绕回组头,保证日志空间可以循环利用。

写入时为防止未刷盘日志被覆盖引入检查点指针checkpoint_LSN,指向当前要擦除的值,一边擦一边往后移(由后台线程不断将buffer pool中的脏页写进ibd文件并且推进)。小于checkpoint_lsn的日志代表已经刷盘,write指针可以覆盖.从而保证崩溃恢复所需的日志永不丢失。

如果写指针追上了检查指针,代表没有空间可以写入,阻塞,需要后台线程将检查指针往前推进一下才可以写入。

redo log如何实现崩溃恢复

  1. 启动的时候innodb检测到非正常关闭,进入crash_recovery流程
  2. 分析阶段扫描redo log 最新的checkpoint开始 重建事务表和脏页表
  3. 重做阶段重做所有已提交但未刷盘的更改
  4. 回退阶段回滚所有未提交的事务
  5. crash_recovery恢复完成后才server才开始接收新连接

redo log主要系统变量:

参数名作用/描述常见生产设置/建议
innodb_log_file_size单个 redo log 文件大小。决定每个日志文件的容量。通常设置为 512MB ~ 4GB,大事务/高并发系统可设更大,避免频繁 checkpoint。
innodb_log_files_in_groupredo log 文件组的数量(循环使用)。通常为 2,MySQL 8.0 默认 2;极端场景可增加以优化 IO 性能。
innodb_flush_log_at_trx_commit事务提交时日志的刷盘策略,决定事务持久化安全性。推荐设为 1(最安全);性能敏感且可接受少量数据丢失时设为 2
innodb_log_buffer_sizeredo 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>.00000nbase_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 数据。介于 01 之间一般业务,允许在可靠性和性能间权衡的场景(如中低频电商等)。

binlog主要的系统变量

变量含义默认值调优意义
log_bin是否开启二进制日志ON复制/增量恢复必须开启
log_bin_basenamebinlog 文件基名 / 路径“binlog”指定写入目录或前缀
max_binlog_size单个 binlog 文件最大大小1 GB控制切换频率,防止单文件过大
sync_binlog每写 N 次事务后 fsync binlog1=1 最安全;=0/>1 提升性能但有数据风险

三个日志在更新流程中的顺序

步骤操作写入目标哪个线程/模块备注
1客户端发起 DMLSQL 接入线程
2生成 Undo logUndo log buffer(内存)→ undo tablespaceInnoDB trx 模块记录旧值,用于回滚和 MVCC;redo 之前必须先有 undo
3生成 Redo logRedo log buffer(内存)InnoDB log_sys 模块物理日志,按 LSN 顺序追加
4修改 Buffer PoolBuffer 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 writerinnodb_flush_log_at_trx_commit 控制;组提交可合并多事务 fsync
8Commit 返回确保 binlog+redo 均已持久
9双写缓冲 & 刷脏页Buffer Pool 脏页 → doublewrite → OS cache → .ibdInnoDB 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存储引擎-第二版》