在之前的文章「简略理解InnoDB底层原理」聊了一下MySQL的Buffer Pool。这里再简略提一嘴,Buffer Pool是MySQL内存构造中非常外围的一个组成,你能够先把它设想成一个黑盒子。

黑盒下的更新数据流程

当咱们查问数据的时候,会先去Buffer Pool中查问。如果Buffer Pool中不存在,存储引擎会先将数据从磁盘加载到Buffer Pool中,而后将数据返回给客户端;同理,当咱们更新某个数据的时候,如果这个数据不存在于Buffer Pool,同样会先数据加载进来,而后批改批改内存的数据。被批改过的数据会在之后对立刷入磁盘。

这个过程看似没啥问题,实则不讲武德。假如咱们批改Buffer Pool中的数据胜利,然而还没来得及将数据刷入磁盘MySQL就挂了怎么办?依照上图的逻辑,此时更新之后的数据只存在于Buffer Pool中,如果此时MySQL宕机了,这部分数据将会永恒的失落;

再者,我更新到一半忽然产生谬误了,想要回滚到更新之前的版本,该怎么办?那不完犊子吗,连数据长久化的保障、事务回滚都做不到还谈什么解体复原?

Redo Log & Undo Log

而通过MySQL可能实现解体复原的事实来看,MySQL必然实现了某些骚操作。没错,这就是接下来咱们要介绍的另外的两个要害性能,Redo LogUndo Log

这两种日志是属于InnoDB存储引擎的日志,和MySQL Server的Binlog不是一个维度的日志。

  1. Redo Log 记录了此次事务 「实现后」 的数据状态,记录的是更新之 「后」 的值
  2. Undo Log 记录了此次事务 「开始前」 的数据状态,记录的是更新之 「前」 的值

所以这两种日志有显著的区别,我用一种更加艰深的例子来解释一下这两种日志。

Redo Log就像你在命令行敲了很长的命令,敲回车执行,后果报错了。此时咱们只须要再敲个↑就会拿到上一条命令,再执行一遍即可。

Undo Log就像你刚刚在Git中Commit了一下,而后再做一个较为简单的改变,然而改着改着你的心态崩了,不想要刚刚的改变了,于是间接git reset --hard $lastCommitId回到了上一个版本。

实现日志后的更新流程

有了Redo Log和Undo Log,咱们再将下面的那张图给欠缺一下。

首先,更新数据还是会判断数据是否存在于Buffer Pool中,不存在则加载。下面咱们提到了回滚的问题,在更新Buffer Pool中的数据之前,咱们须要先将该数据事务开始之前的状态写入Undo Log中。假如更新到一半出错了,咱们就能够通过Undo Log来回滚到事务开始前。

而后执行器会更新Buffer Pool中的数据,胜利更新后会将数据最新状态写入Redo Log Buffer中。因为一个事务中可能波及到屡次读写操作,写入Buffer中分组写入,比起一条条的写入磁盘文件,效率会高很多。

那为什么Undo Log不也搞一个Undo Log Buffer,也给Undo Log提提速,雨露均沾?那咱们假如有这个一个Buffer存在于InnoDB,将事务开始前的数据状态写入了Undo Log Buffer中,而后开始更新数据。

忽然啪一下,很快啊,MySQL因为意外过程退出了,此时会产生一件很难堪的事件,如果更新的数据一部分曾经刷回磁盘了,然而此时事务没有胜利须要回滚,你发现Undo Log随着过程退出一起没了,此时就没有方法通过Undo Log去做回滚。

那如果刚刚更新完内存,MySQL就挂了呢?此时Redo Log Buffer甚至都可能没有写入,即便写入了也没有刷到磁盘,Redo Log也丢了。

其实无所谓,因为意外宕机,该事务没有胜利,既然事务事务没有胜利那就须要回滚,而MySQL重启后会读取磁盘上的Redo Log文件,将其状态给加载到Buffer Pool中。而通过磁盘Redo Log文件复原的状态和宕机前事务开始前的状态是一样的,所以是没有影响的。而后期待事务commit了之后就会将Redo Log和Binlog刷到磁盘。

流程中依然存在的问题

你可能认为到这一步就完满了,事实上则不然。假如咱们在将Redo Log刷入到磁盘之后MySQL忽然宕机了,binlog还没有来得及写入。此时重启,Redo Log所代表的状态就和Binlog所代表的状态不统一了。Redo Log复原到Buffer Pool中的某行的A字段是3,然而任何监听其Binlog的数据库读取进去的数据确是2。

即便Redo Log和Binlog都写入文件了,然而这个时候MySQL所在的物理机或者VM宕机了,日志依然会失落。当初的OS在你写入文件的时候,会先将改变的内容写入的OS Cache中,以此来提高效率。而后依据策略(受你配置的参数的影响)来将OS Cache中的数据刷入磁盘。

基于2PC的一致性保障

从这你能够发现一个要害的问题,那就是必须保障Redo Log和Binlog在事务提交时的数据一致性,要么都存在,要么都不存在。MySQL是通过 2PC(two-phase commit protocol)来实现的。

简略介绍一下2PC,它是一种保障分布式事务数据一致性的协定,它中文名叫两阶段提交,它将分布式事务的提交拆分成了2个阶段,别离是Prepare和Commit/Rollback。

就向两个拳击手开始较量之前,裁判会在两头确认两个选手的状态,相似于问你筹备好了吗?失去确认之后,裁判才会说Fight

裁判询问选手的状态,对应的是第一阶段Prepare;失去了必定的答复之后,裁判发表较量正式开始,对应的是第二阶段Commit,然而如果有一方选手没有筹备好,裁判会发表较量暂停,此时对应的是第一阶段失败的状况,第二阶段的状态会变为Rollback。裁判就对应2PC中的协调者Coordinator,选手就对应参与者Participant

上面咱们通过一张图来看一下整个流程。

Prepare阶段,将Redo Log写入文件,并刷入磁盘,记录上外部XA事务的ID,同时将Redo Log状态设置为Prepare。Redo Log写入胜利后,再将Binlog同样刷入磁盘,记录XA事务ID。

Commit阶段,向磁盘中的Redo Log写入Commit标识,示意事务提交。而后执行器调用存储引擎的接口提交事务。这就是整个过程。

验证2PC机制的可用性

这就是2PC提交Redo Log和Binlog的过程,那在这个期间产生了异样,2PC这套机制真的能保证数据一致性吗?

假如Redo Log刷入胜利了,然而还没来得及刷入Binlog MySQL就挂了。此时重启之后会发现Redo Log并没有Commit标识,此时依据记录的XA事务找到这个事务,进行回滚。

如果Redo Log刷入胜利,而且Binlog也刷入胜利了,然而还没有来得及将Redo Log从Prepare改成Commit MySQL就挂了,此时重启会发现尽管Redo Log没有Commit标识,然而通过XID查问到的Binlog却曾经胜利刷入磁盘了。

此时,尽管Redo Log没有Commit标识,MySQL也要提交这个事务。因为Binlog一旦写入,就可能会被从库或者任何生产Binlog的消费者给生产。如果此时MySQL不提交事务,则可能造成数据不统一。而且目前Redo Log和Binlog从数据层面上,其实曾经Ready了,只是差个标记位。

好了以上就是本篇博客的全部内容了,如果你感觉这篇文章对你有帮忙,还麻烦点个赞关个注分个享留个言

欢送微信搜寻关注【SH的全栈笔记】,查看更多相干文章