17.Update执行流程之redo log的作用

149 阅读10分钟

在上一篇文章中,我们知道mysql为了加速更新的速率,使用了BufferPool和ChangeBuffer。但是现在存在1个问题,不管是使用BufferPool还是使用ChangeBuffer都只是在内存中对数据进行了更新。

现在已经把内存里的数据进行了修改,但是磁盘上的数据还没修改,那么此时万一MySQL所在的机器宕机了,必然会导致内存里修改过的数据丢失,这可怎么办呢?

这个时候,就必须要把对内存所做的修改写入到一个地方里去,这也是内存里的一个缓冲区,组件名字叫redo日志

redolog举例说明

不知道你还记不记得《孔乙己》这篇文章,酒店掌柜有一个粉板,专门用来记录客人的赊账记录。如果赊账的人不多,那么他可以把顾客名和账目写在板上。但如果赊账的人多了,粉板总会有记不下的时候,这个时候掌柜一定还有一个专门记录赊账的账本。

如果有人要赊账或者还账的话,掌柜一般有两种做法:

  • 一种做法是直接把账本翻出来,把这次赊的账加上去或者扣除掉;
  • 另一种做法是先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。

在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作实在是太麻烦了。首先,你得找到这个人的赊账总额那条记录。你想想,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写回到账本上。

这整个过程想想都麻烦。相比之下,还是先在粉板上记一下方便。你想想,如果掌柜没有粉板的帮助,每次记账都得翻账本,效率是不是低得让人难以忍受?

同样,在MySQL里也有这个问题,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程IO成本、查找成本都很高。为了解决这个问题,MySQL的设计者就用了类似酒店掌柜粉板的思路来提升更新效率。

而粉板和账本配合的整个过程,其实就是MySQL里经常说到的WAL技术,WAL的全称是Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,也就是先写粉板,等不忙的时候再写账本。

具体来说,当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。

如果今天赊账的不多,掌柜可以等打烊后再整理。但如果某天赊账的特别多,粉板写满了,又怎么办呢?这个时候掌柜只好放下手中的活儿,把粉板中的一部分赊账记录更新到账本中,然后把这些记录从粉板上擦掉,为记新账腾出空间。

与此类似,InnoDB的redo log是固定大小的,比如可以配置为一组4个文件,每个文件的大小是1GB,那么这块“粉板”总共就可以记录4GB的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。

image.png

write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。checkpoint是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。

write pos和checkpoint之间的是“粉板”上还空着的部分,可以用来记录新的操作。如果write pos追上checkpoint,表示“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把checkpoint推进一下。

有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe

要理解crash-safe这个概念,可以想想我们前面赊账记录的例子。只要赊账记录记在了粉板上或写在了账本上,之后即使掌柜忘记了,比如突然停业几天,恢复生意后依然可以通过账本和粉板上的数据明确赊账账目。

作用1:崩溃恢复

所谓的redo日志,就是记录下来你对数据做了哪些修改,比如对“id=2这行记录修改了name字段的值为"lisi",这就是一个redo日志。

我们先看下图的示意:

image.png

这个redo日志其实是用来在MySQL突然宕机的时候,用来恢复你更新过的新数据。

情况1:事务未提交,MySQL宕机

我们都知道,InnoDB存储引擎是支持事务的,其实在数据库中,哪怕执行一条SQL语句,其实也可以是一个独立的事务,只有当我们提交事务之后,SQL语句才算执行结束。

到目前为止,我们仅是将新数据写入redo日志中,其实还没有提交事务,那么如果此时MySQL崩溃,必然导致内存里Buffer Pool中的修改过的数据都丢失,同时你写入Redo Log Buffer中的redo日志也会丢失,因为这两个Buffer都在内存中。如下图所示:

image.png

那么这个时候数据丢失要紧吗?

其实是不要紧的,因为你一条更新语句,没提交事务,就代表没执行成功,仍然是保持着数据的一致性。因为此时MySQL宕机虽然导致内存里的数据都丢失了,但是你会发现,磁盘上的数据依然还停留在原样子。

也就是说,“id=2”的那行数据的name字段的值还是老的值name="zhangsan",所以此时你的这个事务就是执行失败了,没能成功完成更新,你会收到一个数据库的异常,然后当mysql重启之后,你会发现你的数据并没有任何变化。

所以此时如果mysql服务器宕机,不会有任何的问题。

情况2:事务提交,MySQL宕机

上面是因为没有提交事务,所以mysql服务器宕机影响不大。

但是接着我们准备提交事务了,此时大家可能想到的是我们**是不是应该将redo日志从内存中写入磁盘去?**从而来避免msyql宕机造成的数据不一致。

没错,接着我们要提交事务了,此时存储引擎是要把redo日志从redo log buffer里刷入到磁盘文件里去。在此额外补充一点,内存数据写入磁盘中一般并不是直接写入磁盘,而是先写入磁盘的文件系统缓存中(os cache),再刷入(flush)磁盘中。

崩溃恢复流程

提交事务成功之后,redo日志也写在磁盘文件里,此时你肯定会有一条redo日志记录如下内容:“我此时对哪个数据做了一个什么修改,比如name字段修改为lisi了”。

然后哪怕此时buffer pool中更新过的数据还没刷新到磁盘里去,此时内存里的数据是已经更新过的“name=lisi”,然后磁盘上的数据还是没更新过的“name=zhangsan”。

我们看下图,提交事务之后,可能处于的一个状态,Buffer Pool和redo日志中都是更新后的新值,而磁盘中则是旧值。

image.png

此时如果说提交事务后处于上图的状态,然后mysql系统突然崩溃了,此时会如何?会丢失数据吗?
答案是不会丢失。因此等mysql重启之后,innoDB可以根据redo日志去恢复之前做过的修改,如下图:

image.png

只要你事务提交的时候保证你做的修改以日志形式写入redo log日志,那么哪怕你此时突然宕机了,也没关系!

因为你MySQL重启之后,把你之前事务更新过做的修改根据redo log在Buffer Pool里重做一遍就可以了,就可以恢复出来当时你事务对缓存页做的修改,然后找时机再把缓存页刷入磁盘文件里去。

作用2: 随机写→顺序写

那么有人会问了,你事务提交的时候把修改过的缓存页都刷入磁盘,跟你事务提交的时候把你做的修改的redo log都写入日志文件,他们不都是写磁盘么?差别在哪里?

实际上,如果你把修改过的缓存页都刷入磁盘,这首先缓存页一个就是16kb,数据比较大,刷入磁盘比较耗时,而且你可能就修改了缓存页里的几个字节的数据,难道也把完整的缓存页刷入磁盘吗?

而且你缓存页刷入磁盘是随机写磁盘,性能是很差的,因为他一个缓存页对应的位置可能在磁盘文件的一个随机位置,比如偏移量为45336这个地方。

但是如果是写redo log,第一个一行redo log可能就占据几十个字节,就包含表空间号、数据页号、磁盘文件偏移量、更新值,这个写入磁盘速度很快。

此外,redo log写日志,是顺序写入磁盘文件,每次都是追加到磁盘文件末尾去,速度也是很快的。

所以你提交事务的时候,用redo log的形式记录下来你做的修改,性能会远远超过刷缓存页的方式,这也可以让你的数据库的并发能力更强。

问题:为什么不直接更改磁盘中的数据,而是先在内存中更改,然后再写日志,最后再落盘这么复杂?

注意:如果数据在内存中存在,是先写内存在写日志最后异步刷盘更新数据。

MySQL更改数据的时候,之所以不直接写磁盘文件中的数据,最主要就是性能问题。

因为直接写磁盘文件是随机写,开销大性能低,没办法满足MySQL的性能要求。

所以才会设计成先在内存中对数据进行更改,再异步落盘。

但是内存总是不可靠,万一断电重启,还没来得及落盘的内存数据就会丢失,所以还需要加上写日志这个步骤,万一断电重启,还能通过日志中的记录进行恢复。

写日志虽然也是写磁盘,但是它是顺序写,相比随机写开销更小,能提升语句执行的性能(针对顺序写为什么比随机写更快,可以比喻为你有一个本子,按照顺序一页一页写肯定比写一个字都要找到对应页写快得多)。

这个技术就是大多数存储系统基本都会用的WAL(Write Ahead Log)技术,也称为日志先行的技术,指的是对数据文件进行修改前,必须将修改先记录日志。保证了数据一致性和持久性,并且提升语句执行性能。

随机写会导致磁头不停地换道,造成效率的极大降低

顺序写磁头几乎不用换道,或者换道的时间很短

参考:time.geekbang.org/column/intr…

参考:blog.csdn.net/zht24564812…