MySQL redo log的两阶段提交

Sat, May 8, 2021 阅读时间 1 分钟

WAL技术

WAL(Write-Ahead Logging),就是先些日志,再写磁盘。

当我们向MySQL更新一条记录的时候,InnoDB存储引擎就会把记录顺序的写到redo log中,然后更新InnoDB的buffer pool,并不更新磁盘中的数据页,然后这次更新操作就算是完成了,之后InnoDB存储引擎就会在它有空的时候,把redo log中记录的记录刷到磁盘上的数据页中完成最终的持久化。

由于磁盘的顺序写入效率要远高于随机写入,并且还有组提交等优化,这种方式可以大大提高数据更新时的效率。

关于磁盘的顺序写入,需要注意一下。不是说打开一个文件之后append就是顺序写入了。顺序写入需要知道磁盘上要写入的
位置,直接操作磁盘,向某个特定的地址写入,顺序写入时,磁盘的磁头不需要移动就可以直接写入,这样才有效率的提升。

Redo log

Redo log是Innodb存储引擎提供的用于优化更新操作的日志。它的结构可以看成是个环形的磁盘区域。

比如上图的redo log由四个文件组成,当向redo log中顺序写时,就是依次的写这四个文件,当写满时,就再从第一个文件开始写起。write_pos就是当前磁头的位置,一边写一边后移,checkpoint是当前擦除的位置点,他们之间就是可以写的区域。

Redo log大小是固定的,默认是4GB。Redo log不仅提高了数据更新时的效率,还让InnoDB拥有了crash-safe的能力,即不会因为宕机而丢数据,即如果数据在写入磁盘时宕机,可以通过redo log恢复过来。

Redo log和binlog的不同

  1. redo log是InnoDB特有的,是存储引擎层的,用于crash-safe;binlog是Server层的,所有引擎都可以使用,主要用于数据归档。
  2. redo log是物理逻辑日志,会记录数据页的位置,数据页内部则是逻辑日志,而binlog是单纯的逻辑日志,记录的就是每行的数据。
物理日志:记录的是具体的对磁盘的操作,比如在哪个位置,做了哪个修改
逻辑日志:可以理解为SQL语句,人类是可读的
物理逻辑日志:redo log,记录页位置是物理的,记录数据内容是逻辑的
  1. redo log有固定大小,写满了时需要进行擦除,擦除就要强制刷盘,binlog是没有大小限制的,在写到一定大小时,可以开启一个新文件接着写入。

两阶段提交

由于原本的MySQL只有MyISAM引擎,它不支持crash-safe,所以binlog也没有crash-safe的能力(因为它没有checkpoint,存储引擎不知道该从哪恢复)。而InnoDB存储引擎为了支持crash-safe,又不能改binlog,就提供了Redo log作为保证crash-safe的工具。而这两个日志同时存在,并且都会记录数据的更新操作,所以就得保证两个日志文件上的记录是一致的。两阶段提交解决的就是redo log和binlog的数据一致性的问题

两阶段提交的过程

  1. InnoDB接到了Server层执行器的更新数据的请求,会先将这行数据写入undolog(用于事务回滚的日志)
  2. 然后写入InnoDB的buffer pool中
  3. 然后将这个操作记录到redo log上,此时redo log处于prepare状态,然后InnoDB会告知MySQL的执行器,事务已经执行完了,随时可以提交
  4. MySQL执行器在收到通知后,就会将操作和数据写入binlog,并调用InnoDB的接口进行提交
  5. InnoDB将刚刚写入redo log的语句置为commit状态,本次事务就结束了

下图是一个例子:

两阶段提交的必要性

  • 假如只是先写redo log然后再写binlog

    假如redo log写完,binlog没写完时MySQL挂了,后续恢复时,就可以通过redo log恢复这次的数据,但是binlog中却没有记录,就会导致binlog中和实际数据库中存储的数据不一致。后续如果要使用这个binlog来恢复临时库的话,就会有数据不一致。

  • 假如只是先写binlog然后再写redo log

    假如binlog写完,redolog没写时Mysql挂了,就会导致重启后无法恢复(因为redo log没有),丢一次操作,但是binlog中却记录了这次操作,后续使用binlog恢复临时库时,就会比源库多一次操作出来,导致数据不一致。

我们再观察一下整个过程中的状态变化,以及任意一步出现问题时MySQL的处理

  1. 写入redo log,进入prepare状态
  2. 写入binlog
  3. 写入redo log,进入commit阶段

如果在1和2之间MySQL宕机,则会导致redo log处于prepare阶段,而binlog未写入,此时MySQL重启时,发现redo log处于prepare阶段,就会检查binlog,观察到binlog中没有这条事务的记录,则会忽视这条redo log。redo log和bin log之间通过事务ID对应

事务ID即transaction_id,在每个事务开始时分配,递增且全局唯一。

如果在2和3之间MySQL宕机,则redo log仍是处于prepare阶段,但是binlog已经写入了,此时MySQL重启时,发现redo log处于prepare阶段,就会检查binlog,观察到binlog是完整的,则会自动将这条redo log提交,并恢复数据。

MySQL的两阶段提交实际上是XA规范的应用,两阶段提交是分布式系统维持数据逻辑一致性时的常用方案。