跳到主要内容

redo log 和 bin log

Redo log 和 binlog 的区别

  1. redo log 是 innodb 独有的,binlog 是 mysql server 层的日志所有存储引擎都可以使用。
  2. redo log 是循环写的,binlog 是追加写的。
  3. redo log 为 2 阶段提交,binlog 一次写入。
  4. redo log 保证事务的持久性,binlog 主要用于主从复制,数据恢复。

为什么 redo log 需要二阶段提交?

是为了避免 redo log 与 binlog 出现数据不一致的情况。

在写入其中一个之后,mysql 崩溃会导致另外一个丢失,但是他们的作用又不同,redo log 主要用于保证事务的持久性,binlog 主要用于主从复制和数据恢复。

为什单靠 binlog 没法满足事务?

有这样一个情况,一个事务会执行多条修改语句。

time1: set balance = 100 where id = 1 time2: set balance = 200 where id = 2 time3: 将 id = 1 的修改输入磁盘了 time4: mysql 崩溃(事务未提交,binlog 未写入,但是磁盘中的数据已部分修改)

mysql update 执行语句过程。

mysql 执行 update 语句的时候,会先找到这条数据,然后执行更新操作,把更新之后的数据写入内存,同时,写入 redo log,并将其标记为 prepare 状态,在事务提交时,先写入 bin log, 在将 redo log 改为 commit 状态。

事务执行

  1. 从磁盘读取数据页到 Buffer Pool
  2. 在 Buffer Pool 中修改数据
  3. I这个页变成脏页
  4. 写 undo log
  5. 写 redo log buffer

事务提交 6. 写 redo log, 并标记为 prepare 状态 7. bin log 写入 9. redo log 改为 commit 状态

某个时刻,后台线程把脏页写入磁盘的 .ibd 文件

WAL 机制

Write-Ahead Logging: 先写日志,在写入磁盘。

先把日志写入 redo log,再把修改刷入磁盘数据页。

  1. 写入 redo log buffer
  2. 写入 OS Cache
  3. 写入磁盘
  4. 在后台异步将脏数据刷到磁盘

崩溃后的恢复机制

mysql 崩溃后会根据 redo log 来恢复数据。

如果看到 redo log 是 prepare 状态时,会去检查 bin log 是否完整,如果 bin log 已经写入,则提交这个事务,否则回滚这个事务。 如果看到 redo log 是 commit 状态,就会直接提交事务。

相关的 mysql 配置参数

sync_binlog

binlog 什么时候写入磁盘

# sync_binlog配置
sync_binlog=1 # (fsync) 每次提交都刷盘
sync_binlog=0 # (写入 OS Cache)由操作系统决定何时刷盘
sync_binlog=N # 每 N 个事务刷一次盘

innodb_flush_log_at_trx_commit

innodb_flush_log_at_trx_commit 用于控制 redo log 什么时候写入磁盘。

mysql 内存 ---> 操作系统缓存(OS Cache = Page Cache) ---> 磁盘

  • mysql 内存: 指的是 INNODB buffer bool,redo log buffer 等
  • 操作系统缓存: 用于临时存放要写到磁盘的数据。

通过 fsync()/fdatasync() 系统调用,会将 OS Cache 的数据写入磁盘。

innodb_flush_log_at_trx_commit=1 # 每次都写入磁盘
innodb_flush_log_at_trx_commit=0 # 写入MySQL 内存,每隔 1s 将数据写入操作系统缓存,并调用 fsync 将数据刷入磁盘。====> mysql 崩溃时可能会丢失 1s 内的事务
innodb_flush_log_at_trx_commit=2 # 写入操作系统缓存,mysql 后台线程每秒执行一次 fsync 将数据刷入磁盘。====> mysql 崩溃不会丢失数据,系统断电可能会丢失 1s 内的事务

redo log 的 prepare 和 commit 阶段都是受到 innodb_flush_log_at_trx_commit 参数的控制。

group commit (组提交)优化

每次事务提交都需要把日志写入磁盘(调用 fsync)。 但是 sync 很慢,在高并发时会出现性能瓶颈。

group commit 就是将一段时间内的 fsync 操作合并,一次性写入磁盘。

具体的流程是,选择 一个 leader ,根据参数来控制 group commit的时机。

binlog_group_commit_sync_delay 控制 leader 等待的时间 binlog_group_commit_sync_no_delay_count 控制累计的事务数量

2 者满其一就执行 fsync 将日志刷入磁盘。

redo log prepare, binlog, redo log commit 三个阶段都会出现 group commit。

但是 redo log commit 阶段是没必要的。 因为 binlog 都写入了,表示这个事务已经提交了,redo log commit 阶段丢失也是没有问题的。

group commit 相关参数设置

对于金融行业,不推荐设置组提交相关参数。其余行业可以使用下面的推荐。

# 组提交优化:增大组大小
binlog_group_commit_sync_delay = 1000 # 延迟 1ms(1000 微秒)⭐
binlog_group_commit_sync_no_delay_count = 10 # 或累积 10 个事务

update 语句执行过程

UPDATE t SET c = 1 WHERE id = 1;
COMMIT;

mysql 处理上面语句的过程。

UPDATE t SET c = 1 WHERE id = 1;

  1. 找到 id =1 的记录
  2. 对这条数据加锁
  3. 将数据写入 undo 日志
  4. 在内存中更新数据
  5. 写入 redo log,标记为 prepare 状态

commit

  1. 写入 binlog
  2. 将 redo log 标记为 commit 状态
  3. 释放锁
  4. 告诉客户端,执行完成

mysql 会异步把数据写入磁盘

innodb_flush_log_at_trx_commit

面试题

请详细说明 MySQL 中 redo log 和 binlog 的区别,并解释为什么需要两阶段提交(two-phase commit)?如果没有两阶段提交会出现什么问题?

redo log 是 innodb 特有的,主要用来实现事务的 crash-safe 的。 binlog 是 mysql server 层级的,所有引擎都可以使用。redo log 是循环写,binlog 是追加写。redo log 记录的是物理层面的修改,binlog 记录的是逻辑修改。binlog 主要用于主从复制和数据恢复的。redo log 的写入是分为 2 个阶段的。 具体的流程是这样的。

执行 update 语句,执行器通过引擎查询数据,在内存中修改这条数据,写入 redo log 日志,标记为 prepare 状态,在事务提交时,先写入 binlog 在将对应的 redo log 修改为 commit 状态。 这样做的目的是为了保证事务的持久性。在任何一个阶段 mysql 崩溃都能保证事务的持久性。

在 mysql 崩溃后重启会读取 redo log,判断 redo log 的状态,如果是 commit 状态,则提交事务,为 prepare 状态则去查询对应的 binlog 是否写入,若已写入则提交事务,否则回滚事务。

redo log 与 binlog 记录内容的区别

redo log 记录的是物理日志,例如:将数据页300,偏移量500,将 4 字节数据从 0x11 改为 0x12。

binlog 记录的是逻辑日志,有三种格式

  • statement: 记录 sql,如 update user set age = 18 where id = 1
  • row: 记录变化,如 将 id = 1 的记录的 age 字段改为 18
  • mixed: 混合使用 statement 和 row 格式