本文转载自微信公众号《SH的全栈笔记》,作者SH。转载本文请联系SH全栈笔记公众号。黑盒下更新数据的过程当我们查询数据的时候,首先会在BufferPool中进行查询。如果BufferPool不存在,存储引擎会先将数据从磁盘加载到BufferPool中,然后再将数据返回给客户端;同理,当我们更新某个数据时,如果BufferPool中不存在该数据,也会先加载该数据,然后修改修改内存中的数据。修改后的数据稍后会刷新到磁盘。MySQL崩溃恢复的过程看似没问题,但不谈武功。假设我们成功修改了BufferPool中的数据,但是如果MySQL在刷新数据到磁盘之前就挂了怎么办?按照上图中的逻辑,此时更新的数据只存在于BufferPool中。如果这时候MySQL死机了,电脑死机了,这部分数据就会永久丢失;而且我更新到一半突然出错,想回滚到更新前的版本,怎么办?这还没完,连数据持久化的保证,事务回滚都做不到,崩溃恢复呢?RedoLog&UndoLog而MySQL能够实现崩溃恢复,肯定是MySQL做了一些花哨的操作。没错,这就是我们接下来要介绍的另外两个重点功能,RedoLog和UndoLog。这两类日志是属于InnoDB存储引擎的日志,与MySQLServer的Binlog不是同维度的日志。RedoLog记录事务“完成后”的数据状态,记录更新“之后”的值。UndoLog记录的是事务“开始前”的数据状态,而记录的是更新“前”的值,所以这两类日志有着明显的区别。我会用一个比较通俗的例子来解释这两类日志。RedoLog就像你在命令行敲了一个很长的命令,回车执行,结果报错。此时,我们只需要再次输入↑,就可以得到之前的命令,再次执行。UndoLog就像你在Git中刚刚做了一个Commit,然后做了一个比较复杂的改动,但是改完之后,你的心态就崩溃了,你不想要刚才的改动,所以你就gitreset--hard$lastCommitId并返回到以前的版本。日志实现后的更新过程有了RedoLog和UndoLog,我们来完善一下上图。MySQL崩溃恢复首先更新数据还是会判断BufferPool中是否存在该数据,不存在则加载。上面我们提到了回滚问题。在更新BufferPool中的数据之前,我们需要将数据事务开始前的状态写入到UndoLog中。假设更新中途出现错误,我们可以使用UndoLog回滚到事务开始之前。然后executor会更新BufferPool中的数据,更新成功后将数据的最新状态写入RedoLogBuffer。因为一个事务中可能涉及多次读写操作,所以成组写入Buffer比一个一个写入磁盘文件效率要高得多。redo-log-buffer那为什么UndoLog不也创建一个UndoLogBuffer,这样同样加速UndoLog,雨露均匀呢?那么我们假设在InnoDB中存在一个Buffer,将事务开始前的数据状态写入到UndoLogBuffer中,然后开始更新数据。突然点击,很快,MySQL因为一个意外的进程退出了,这时候会发生一件很尴尬的事情,如果更新的部分数据已经刷回磁盘了,但是这时候事务没有成功,需要被回滚,你发现Undolog随着进程退出而消失,这时候没办法通过UndoLog回滚。MySQL刚更新完内存就挂了怎么办?这时候RedoLogBuffer可能连写都没有,即使写了也没有刷到磁盘,RedoLog就丢失了。其实没关系,因为意外宕机,交易没有成功。由于事务没有成功,所以需要回滚。MySQL重启后,会读取磁盘上的RedoLog文件,并将其状态加载到BufferPool中。但是通过磁盘RedoLog文件恢复的状态和宕机前事务开始前的状态是一样的,所以没有影响。然后等待事务提交后,将RedoLog和Binlog刷到磁盘。过程中还存在的问题你可能认为这一步很完美,其实不然。假设MySQL在刷RedoLog到磁盘后突然死机,此时binlog还没来得及写入。此时重启,RedoLog代表的状态和Binlog代表的状态不一致。从RedoLog恢复到BufferPool中的一行的A字段是3,但是任何一个监听其Binlog的数据库读取到的数据确实是2。即使RedoLog和Binlog都写入了文件,但是此时MySQL所在的物理机或者VM宕机,日志还是会丢失。当你写入一个文件时,当前的OS会先将改变的内容写入到OSCache中,以提高效率。然后根据策略(受你配置的参数影响),将OSCache中的数据刷新到磁盘中。2PC-BasedConsistencyGuarantee从这里可以发现一个关键问题,就是RedoLog和Binlog在事务提交的时候必须保证数据的一致性,要么都存在,要么不存在。MySQL是通过**2PC(两阶段提交协议)**实现的。MySQL崩溃恢复简单介绍一下2PC,它是一种保证分布式事务数据一致性的协议。它的中文名称是两阶段提交。它将分布式事务的提交拆分为两个阶段,即Prepare和Commit/Rollback。在两位拳手开始比赛之前,裁判会确认中间两位拳手的状态,这类似于问你准备好了吗?确认后,裁判会说Fight。裁判询问队员状态,对应第一阶段Prepare;得到肯定答复后,裁判宣布比赛正式开始,对应第二阶段Commit,但若有选手未准备好,裁判将宣布暂停比赛。此时对应第一阶段失败,第二阶段的状态会变为Rollback。2PC中裁判对应Coordinator,选手对应Participant。我们通过一张图来看一下整个过程。2PC刷入磁盘Prepare阶段,将RedoLog写入文件,刷入磁盘,记录内部XA事务的ID,设置RedoLog状态为Prepare。RedoLog写入成功后,Binlog也会刷入磁盘记录XA事务ID。在Commit阶段,将Commit标志写入磁盘中的RedoLog,表示事务已提交。然后执行器调用存储引擎的接口提交事务。这就是整个过程。验证2PC机制的可用性。这就是2PC提交RedoLog和Binlog的过程。这期间,发生了异常。2PC机制真的能保证数据的一致性吗?假设刷入RedoLog成功,但是还没来得及刷入BinlogMySQL就挂了。这时候重启后,你会发现RedoLog没有Commit标记了。这时候可以根据记录的XA事务找到这个事务并回滚。如果RedoLog刷入成功,Binlog也刷入成功,但是还没来,RedoLog从Prepare变成Commit,MySQL就会挂掉。这时候重启后你会发现虽然RedoLog没有Commit标识,但是可以通过XID查询到接收到的Binlog已经成功刷入磁盘。此时虽然RedoLog没有Commit标志,但是MySQL也提交了这个事务。因为Binlog一旦写入,就可能被从库或任何消费Binlog的消费者消费。如果此时MySQL不提交事务,可能会造成数据不一致。而且目前RedoLog和Binlog从数据层面上已经是Ready了,只是少了一个flag。
