mysql undolog redolog - yaokun123/php-wiki GitHub Wiki

Mysql基于Redo Log和Undo Log的MySQL崩溃恢复流程

在之前的文章聊了一下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回到了上一个版本。

2.1、Redo Log

重做日志 ib_logfile0 ib_logfile1 采用循环写的模式。redo log中记录的是对哪一页,哪一条数据做了什么修改。redo log 文件顺序写,效率比随机写效率高。

innodb_log_file_size 4294967296        // redo log 每个文件的大小
innodb_log_files_in_group 2            // redo log一共使用几个文件

将这俩参数相乘即可得到总的可用Redo log 空间

log buffer

innodb_log_buffer_size 8388608        // log buffer的大小
innodb_flush_log_at_trx_commit 1      // 什么时候将redo log buffer中的数据写到redo log文件中

0:每隔1s会将log buffer中的数据写入到文件,同时还会通知文件系统进行文件同步的flush。最极端的情况是丢失1s时间的数据变更
1:默认,commit时候即同步到文件中。不会丢失任何已经提交的数据
2:commit时候调用了文件系统的文件写入操作。而我们的文件系统都是有缓存机制的,由操作系统控制落盘。

redo log 从log buffer中刷盘时机
1、log buffer空间不足时。log buffer的大小是有限的,如果不停的往这个有限大小的log buffer里塞入日志,很快它就会被填满。
所以如果当前写入log buffer的redo日志已经占满了log buffer总容量的大约一半左右,就需要把这些日志刷新到磁盘上。
2、提交事务时。
3、后台线程不停的刷。大约每秒都会刷一次。
4、正常关闭服务器
5、checkpoint时

log buffe是redo log的缓冲区 重写日志也会先写入缓冲区 然后慢慢处理,log buffer也是buffer poll中的一部分,可以配置。 当innodb在缓冲池变更数据时,首先先把相关的变更写入重做日志缓冲中redo log buffer中。然后再按时或者提交事务时写入磁盘,完成真正的磁盘修改

加上redo log后的更新流程

  • 1、查看要更新的数据也是否存在buffer pool中
  • 2、如果不存在就从磁盘读取放到buffer pool
  • 3、如果存在就直接修改对应的数据页(内存)---- 修改后的页叫做脏页
  • 4、在修改数据页之前,应先生成redo log,把redo log写入缓冲区log buffer
  • 5、当用户commit时,需要把log buffer中的redo log写入到redo log文件中(这一步是需要配置innodb_flush_log_at_trx_commit=1)
  • 6、如果redo log写入成功代表commit成功
  • 7、如果redo log写入失败否则rollback回滚

三、脏页落盘

读取操作

将从磁盘中读到的页放在缓冲池中,下次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。

修改操作

首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为CheckPoint的机制刷新回磁盘。

3.1、CheckPoint

思考一下这个场景:如果重做日志可以无限地增大,同时缓冲池也足够大,那么是不需要将缓冲池中页的新版本刷新回磁盘。因为当发生宕机时,完全可以通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。

因此Checkpoint(检查点)技术就诞生了,目的是解决以下几个问题:1、缩短数据库的恢复时间;2、缓冲池不够用时,将脏页刷新到磁盘;3、重做日志不可用时,刷新脏页。

脏页落盘的时机 采用CheckPoint检查点机制 以下机制都可通过参数控制

sharp checkpoint:强制落盘。把内存中所有的脏页都执行落盘操作。只有当关闭数据库之前才会执行。

fuzzy checkpoint:没落落盘。把一部分脏页执行落盘操作
	1.Master Thrad Checkpoint 主线程定时将脏页写入磁盘 每秒或每10s执行一次脏页。
	2.FLUSH_LRU_LIST buffer pool有脏页换出,执行落盘
	3.Async/Sync Flush checkpoint 当redo log快写满的时候执行落盘
		a.当redo log超过75%小于90%会执行异步落盘
		b.当redo log超过90%,会执行同步落盘操作。会阻塞写操作。
	4.Dirty Page too much checkpoint 如果buffer pool中脏页太多,脏页率超过75%执行落盘

四、实现日志后的更新流程

有了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了,只是差个标志位。

https://mp.weixin.qq.com/s?__biz=MzU5NDk0MTc2OA==&mid=2247484320&idx=1&sn=4122faacb06a3a2594943b74fb28d382&chksm=fe78c187c90f48914da1414451757a62b1c3915c4adc117f34165c47192e84c4b1093701197b&cur_album_id=1690683025673535494&scene=190#rd