redo log 两阶段提交
redo log 与 binlog
redo log(重做日志)让InnoDB存储引擎拥有了崩溃恢复能力。binlog(归档日志)保证了MySQL集群架构的数据一致性。- 虽然它们都属于持久化的保证,但是侧重点不同。
Before
redo log 在事务执行过程中可以不断写入,而 binlog 只有在提交事务时才写入,所以 redo log 与 binlog 的写入时机不一样。
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 的写入拆成了两个步骤 prepare 和 commit,这就是两阶段提交。
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,或者采用反过来的顺序。我们看看这两种方式会有什么问题。
-
先写 redo log 后写 binlog。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。
但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。
然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。 -
先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。