共计 3063 个字符,预计需要花费 8 分钟才能阅读完成。
在之前的文章「简略理解 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 Log和Undo Log。
这两种日志是属于 InnoDB 存储引擎的日志,和 MySQL Server 的 Binlog 不是一个维度的日志。
- Redo Log 记录了此次事务 「实现后」 的数据状态,记录的是更新之 「后」 的值
- 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 的全栈笔记】,查看更多相干文章