我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,[点击查看活动详情]
在讲解两阶段提交之前,需要对MySQL中的binlog、redo log和undo log有一定的了解。
在MySQL中有三个比较重要的日志:bin log(二进制日志)、redo log(重做日志)、undo log(回滚日志)。
binlog逻辑日志,记录的是mysql语句的原始逻辑,适用于维护集群内数据的一致性;redo log用于崩溃恢复,属于 InnoDB 存储引擎特有日志,让mysql有崩溃后恢复的crash-safe能力,维护数据的持久性;undo log记录了数据的逻辑变化,就是为了回滚事务用的,实现数据的原子性,undo log也是 MVCC(多版本并发控制) 实现的关键,所有锁多版本最终都是为了实现数据的一致性。
一、什么是两阶段提交
当有数据修改时,会先将修改redo log cache和binlog cache然后在刷入到磁盘形成redo log file,当redo log file全都刷入到磁盘时(prepare 状态)和提交成功后才能将binlog cache刷入磁盘,当binlog全部刷新到磁盘后会记录一个xid,然后在relo log file上打上commit标志(commit阶段)。
二、两阶段提交的提交的流程
下面执行器和 InnoDB 执行 Update 时内部流程:
我们来看看更新 update T set age=age+1 where age=23语句流程:
以更新 update T set c=c+1 where ID=1; 语句为例:
- 执行器通过 InooDB 引擎去 ID 所在行,ID 为主键。引擎通过树搜索找到该行,如果该行所在数据页在内存中,返回给执行器。否则先从磁盘读入内存,然后再返回。
- 执行器拿到引擎给的数据,将 C 值加 1,等到新的一行,然后通过引擎接口重新写入新数据。
- 引擎将该行更新到内存中,同时将该更新操作记录到 redo log 中,并更改 redo log 的状态为 prepare 状态。然后告知执行器,在合适的时间提交事务。
- 执行器生成这个操作的 binlog,并将 binlog 写入磁盘。
- 执行器调用引擎到的提交事务接口,将刚刚写入的 redo log 改成 commit 状态,更新完成。
浅色为执行器执行,深色为引擎执行
在更新内存后,将写入 redo log 拆分了成两个步骤:prepare 和 commit,就是常说的两阶段提交。用于保证当有意外情况发生时,数据一致性。
三、如果不采用两阶段提交会发生什么
1、先写 redo log 后写 binlog. 假设在写入 redo log 后,MySQL 发生异常重启,此时 binlog 没有写入。在重启后,由于 redolog 已经写入,此时数据库的内容是没有问题的。但此时,如果想要拿 binlog 进行备份或恢复,发现会少了最后一条的更新逻辑,导致数据不一致。 2、先写 binlog 后写 redo log. binlog 写入后,MySQL 异常重启,redo log 没有写入。此时重启后,发现 redo log 没有成功写入,认为这个事务无效,而此时 binlog 却多了一条更新语句,拿去恢复后自然数据也是不一致的。
再分析下两阶段提交的过程:
1.在写 redo log prepare 阶段崩溃,时刻 A 的位置。重启后,发现 redo log 没写入,回滚此次事务。 2.如果在写 binlog 时崩溃,重启后,发现 binlog 未被写入,回滚操作。
3.binlog 写完,但在提交 redo log 的 commit 状态时发生 crash
- 如果 redo log 中事务完整,有了 commit 标识,直接提交。
- 如果 redo log 中只有完整的 prepare, 判断对应 binlog 是否完整。
完整,提交事务 不完整,回滚事务。
如何判断 binlog 是否完整?
- statement 格式 binlog,会有 COMMIT; 标识
- row 格式的 binlog,会有 XID event. 标识
- 在 5.6 后,还有 binlog-checksum 参数,验证 binlog 正确性。
如何将 redo log 和 binlog 关联表示同一个操作?
结构中有一个共同的数据字段,XID. 在崩溃恢复时,会按顺序扫描 redo log:
- 如果有 prepare,又有 commit 的 redo log,直接提交。
- 如果只有 prepare,没有 commit 的 redo log, 拿 XID 去 binlog 找对应的事务做判断。
数据写入后,最终落盘和 redo log 有无关系?
- 对于正常运行的 instance 来说,内存中页被修改后,和磁盘的数据页不一致,称为脏页。而落盘的过程,是把内存中的数据页写入磁盘。
- 对于 crash 场景,InnoDB 判断一个数据页是否丢失了更新,会将其读到内存,然后让 redo log 更新内存内容。更新完成后,内存页就变成脏页,然后回到第一种情况的状态。
redo log buffer 和 redo log 的关系?
在一个事务的更新过程中,存在多个 SQL 语句,所以是要写多次日志的。 但在写的过程中,生产的日志要先保存起来,但在 commit 前,不能直接写到 redo log 中。 所以通过内存中 redo log buffer 先存 redo log 的日志。在 commit 时,将 buffer 中的内容写入 redo log。