redo log 两阶段提交

redo log 与 binlog

  • redo log (重做日志)让 InnoDB 存储引擎拥有了崩溃恢复能力。
  • binlog (归档日志)保证了 MySQL 集群架构的数据一致性。
  • 虽然它们都属于持久化的保证,但是侧重点不同。

Before

redo log 在事务执行过程中可以不断写入,而 binlog 只有在提交事务时才写入,所以 redo logbinlog 的写入时机不一样。

flowchart LR
 subgraph Z [事务]
 A[开始事务] --> B[更新数据] --> C[提交事务]
 end
 subgraph Y ["Server 层"]
 D[执行器]
 E["写入binlog"]
 end
 subgraph X ["存储引擎层"]
 F[InnoDB]
 G["写入redo log"]
 end
 C --> Y
 Z --> X

数据不一致问题

由于两者写入时机不一样,因此可能出现通过日志获得的结果不一致。

例如,修改 table 中的数据,redo log 写日志正常,binlog 写日志出错。数据恢复时,主数据库数据是修改后的数据,从/备份数据库数据是修改前的数据,数据不一致。

解决方案 - 两阶段提交

InnoDB 存储引擎使用两阶段提交方案。

原理很简单,将 redo log 的写入拆成了两个步骤 preparecommit,这就是两阶段提交

flowchart LR
 A[开始事务] --> B[更新数据] --> C["写入redo log(Prepare 阶段)"]  --> D[提交事务] 
 subgraph D [提交事务]
 E["写入binlog"] --> F["redo log 设置 commit 阶段"]  
 end

两阶段提交 - redo log 设置 commit 正常

直接恢复数据。

两阶段提交 - redo log 设置 commit 异常

虽然 redo log 是处于 prepare 阶段,但是能通过事务 id 找到对应的 binlog 日志,所以 MySQL 认为是完整的,就会提交事务恢复数据。

两阶段提交 - binlog 异常

因为 MySQL 根据 redo log 日志恢复数据时,发现 redo log 还处于 prepare 阶段,并且没有对应 binlog 日志,就会回滚该事务。

redo log 容灾恢复

MySQL 的处理过程如下

  • 判断 redo log 是否完整,如果判断是完整 commit 的,直接用 redo log 恢复
  • 如果 redo log 只是预提交 prepare 但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log ,用 redo log 恢复,不完整就回滚事务,丢弃数据。

只有在 redo log 状态为 prepare 时,才会去检查 binlog 是否存在,否则只校验 redo log 是否是 commit 就可以啦。怎么检查 binlog :一个完整事务 binlog 结尾有固定的格式。

为什么使用两阶段提交

保证两个日志一致性

(1) 先写 redo log 再写 binlog,若 redo log 完成,binlog 没有完成,那么主机数据恢复正常,但从机使用 binlog 数据与主机数据不一致;

(2) 先写 binlog 先写 redolog 再写,同样不一致。

如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。

  1. 先写 redo log 后写 binlog。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。
    但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。
    然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。

  2. 先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。