redo log 和 bin log
Redo log 和 binlog 的区别
- redo log 是 innodb 独有的,binlog 是 mysql server 层的日志所有存储引擎都可以使用。
- redo log 是循环写的,binlog 是追加写的。
- redo log 为 2 阶段提交,binlog 一次写入。
- 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 状态。
事务执行
- 从磁盘读取数据页到 Buffer Pool
- 在 Buffer Pool 中修改数据
- I这个页变成脏页
- 写 undo log
- 写 redo log buffer
事务提交 6. 写 redo log, 并标记为 prepare 状态 7. bin log 写入 9. redo log 改为 commit 状态
某个时刻,后台线程把脏页写入磁盘的 .ibd 文件
WAL 机制
Write-Ahead Logging: 先写日志,在写入磁 盘。
先把日志写入 redo log,再把修改刷入磁盘数据页。
- 写入 redo log buffer
- 写入 OS Cache
- 写入磁盘
- 在后台异步将脏数据刷到磁盘
崩溃后的恢复机制
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;
- 找到 id =1 的记录
- 对这条数据加锁
- 将数据写入 undo 日志
- 在内存中更新数据
- 写入 redo log,标记为 prepare 状态
commit
- 写入 binlog
- 将 redo log 标记为 commit 状态
- 释放锁
- 告诉客户端,执行完成
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 格式