MySQL的 redo log两阶段提交深入理解

总结摘要
MySQL的redo_log两阶段提交深入理解

在 MySQL 的世界里,保证数据不丢且逻辑一致,是数据库最基础也最重要的能力。为了实现这个目标,MySQL 引入了一个非常巧妙的设计——redo log 的两阶段提交。

为什么需要两阶段提交?

这事得从 MySQL 的两套日志系统说起。一套是 InnoDB 存储引擎的 redo log,负责保证事务的持久性——只要事务提交成功,数据就不会丢。另一套是 server 层的 binlog,负责数据复制和恢复——主从同步、数据恢复都靠它。

问题在于,这两套日志是独立写入的。如果写日志的过程中数据库宕机了,就可能出现一个日志写了,另一个没写的情况。

举个具体的例子:

假设你要把某行数据的 age 从 18 改成 20。如果 MySQL 先写 redo log,写完还没来得及写 binlog 就宕机了,重启后 InnoDB 会通过 redo log 把数据恢复成 20,但 binlog 里却没有这条记录。从库同步 binlog 时就会错过这次更新,导致主从数据不一致。

反过来,如果先写 binlog 再写 redo log,写 binlog 后宕机,重启后 InnoDB 发现 redo log 里没有记录,事务无效,数据还是 18,但 binlog 里却记录着把数据改成了 20。从库一看 binlog,也跟着改成 20,主库数据是 18,从库是 20,又对不上了。

两阶段提交就是为了解决这个问题而生的。

两阶段提交到底是怎么做的?

两阶段提交把事务的提交过程拆成了两个阶段:prepare 阶段和 commit 阶段。我们还是拿刚才那个 update 语句来看,整个流程是这样的:

事务一开始,InnoDB 正常执行更新操作,在内存里修改数据,同时生成一条 redo log。但这时的 redo log 处于 prepare 状态,还没落盘。

接着进入 prepare 阶段:InnoDB 把这个 prepare 状态的 redo log 刷到磁盘上。注意,这一步很关键——它把事务的执行结果固化下来了,即使现在数据库宕机,等重启后也能根据这条 redo log 把数据恢复出来。

prepare 阶段完成后,进入 commit 阶段的第一步:server 层把这条事务的 binlog 也刷到磁盘上。binlog 里记录着刚才那条 update 语句的完整信息,还带着一个全局唯一的 XID,用来和 redo log 对应起来。

最后是 commit 阶段的第二步:InnoDB 收到 binlog 写入成功的消息后,在 redo log 里打上一个 commit 标记,也刷到磁盘。到这一步,整个事务才算真正提交成功,MySQL 才会告诉客户端:你的数据改好了。

崩溃恢复时怎么判断该不该提交?

两阶段提交最精妙的地方,在于崩溃恢复的逻辑。

假设数据库在事务执行过程中突然宕机了,重启后进入恢复流程,InnoDB 会扫描所有处于 prepare 状态的事务,这时候 binlog 就成了"最终裁决者"。

恢复的逻辑其实很简单:

  • 如果这条事务的 binlog 完整存在(通过 XID 匹配),那就把事务提交掉,在 redo log 里补上 commit 标记。
  • 如果 binlog 不存在或者不完整,那就把事务回滚掉。

这个逻辑为什么成立?因为 binlog 写入磁盘的时刻,就是事务的"分水岭"。binlog 一旦落盘,说明事务已经走过了最关键的环节,理应被完成。如果 binlog 还没写,说明事务还没到那一步,直接回滚也没问题。

换句话说,在 MySQL 的世界里,事务是否真正提交,看的不是 redo log 的 commit 标记,而是 binlog 有没有完整地写进去。

性能怎么办?组提交来救场

看到这儿你可能会想:每个事务都要刷两次盘,redo log 刷一次,binlog 刷一次,这性能能扛得住吗?

确实,如果每个事务都单独刷盘,在高并发场景下磁盘 I/O 会成瓶颈。所以 MySQL 搞了个优化机制,叫组提交。

组提交的思路是把多个事务的刷盘操作合并成一次。比如 10 个事务同时提交,本来需要 10 次刷盘,现在由一个线程作为"组长",代表这组事务去刷盘,其他线程等着就行。刷盘完成后,组长通知大家:你们的日志都已经安全落地了。

在 MySQL 5.6 之后,binlog 的组提交还进一步细化成了三个阶段:Flush、Sync、Commit。每个阶段都可以合并操作,进一步提升了并发性能。

所以两阶段提交和组提交是相辅相成的——前者保证一致性,后者保证性能,共同撑起了 MySQL 的事务能力。

写在最后

redo log 的两阶段提交,本质上是在分布式系统的思路上解决本地问题——把单个数据库里的两套日志当作两个独立的节点,用两阶段提交的协议来保证它们的一致性。

这个设计虽然增加了实现的复杂度,但换来了强大的崩溃恢复能力。理解了它,你也就理解了为什么 MySQL 能在各种异常情况下依然保持数据的一致。