1、 redo log 这个机制存在的意义

首先我们都知道,执行增删改SQL语句的时候,都是针对一个表中的某些数据去执行的,此时的话,首先必须找到这 个表对应的表空间,然后找到表空间对应的磁盘文件,接着从磁盘文件里把你要更新的那批数据所在的数据页从磁盘 读取出来,放到Buffer Pool的缓存页里去

接着实际上你的增删改SQL语句就会针对Buffer Pool中的缓存页去执行你的更新逻辑,比如插入一行数据,或者更新 一行数据,或者是删除一行数据。

当然,删除数据的逻辑我们还没讲,后续很快就要讲到了。至于说数据页和数据行的格式,就不用我多说了,其实都 是MySQL自己定义的, 会更新free链表、flush链表、lru链 表,然后有专门的后台IO线程,不定时的根据flush链表、lru链表,会把你更新过的缓存页刷新回磁盘文件的数据页里 去

所以大家都知道这个机制里最大的漏洞就在于,万一你一个事务里有增删改SQL更新了缓存页,然后事务提交了,结果万一你还没来得 及让IO线程把缓存页刷新到磁盘文件里,此时MySQL宕机了,然后内存数据丢失,你事务更新的数据就丢失了! 但是也不可能每次你事务一提交,就把你事务更新的缓存页都刷新回磁盘文件里去,因为大家之前也都知道,缓存页 刷新到磁盘文件里,是随机磁盘读写,性能是相当的差!这会导致你数据库性能和并发能力都很弱的! 所以此时才会引入一个redo log机制,这个机制就是说,你提交事务的时候,绝对是保证把你对缓存页做的修改以日 志的形式,写入到redo log日志文件里去的 这种日志大致的格式如下:对表空间XX中的数据页XX中的偏移量为XXXX的地方更新了数据XXX。

只要你事务提交的时候保证你做的修改以日志形式写入redo log日志,那么哪怕你此时突然宕机了,也没关系! 因为你MySQL重启之后,把你之前事务更新过做的修改根据redo log在Buffer Pool里重做一遍就可以了,就可以恢复 出来当时你事务对缓存页做的修改,然后找时机再把缓存页刷入磁盘文件里去。 那么有人会问了,你事务提交的时候把修改过的缓存页都刷入磁盘,跟你事务提交的时候把你做的修改的redo log都 写入日志文件,他们不都是写磁盘么?差别在哪里? 这是本文一个关键的问题。 实际上,如果你把修改过的缓存页都刷入磁盘,这首先缓存页一个就是16kb,数据比较大,刷入磁盘比较耗时,而且 你可能就修改了缓存页里的几个字节的数据,难道也把完整的缓存页刷入磁盘吗? 而且你缓存页刷入磁盘是随机写磁盘,性能是很差的,因为他一个缓存页对应的位置可能在磁盘文件的一个随机位 置,比如偏移量为45336这个地方。 但是如果是写redo log,第一个一行redo log可能就占据几十个字节,就包含表空间好、数据页号、磁盘文件偏移 量、更新值,这个写入磁盘速度很快。 此外,redo log写日志,是顺序写入磁盘文件,每次都是追加到磁盘文件末尾去,速度也是很快的。 所以你提交事务的时候,用redo log的形式记录下来你做的修改,性能会远远超过刷缓存页的方式,这也可以让你的 数据库的并发能力更强。

2、 写入日志文件的redo log长什么样?

之前略微给大家提到过,就是redo log里本质上记录的就是在对某个表空间的某个数据页的某个偏移量的地方修改了 几个字节的值,具体修改的值是什么,他里面需要记录的就是表空间号+数据页号+偏移量+修改几个字节的值+具体 的值 所以根据你修改了数据页里的几个字节的值,redo log就划分为了不同的类型,MLOG_1BYTE类型的日志指的就是修 改了1个字节的值,MLOG_2BYTE类型的日志指的就是修改了2个字节的值,以此类推,还有修改了4个字节的值的日 志类型,修改了8个字节的值的日志类型。 当然,如果你要是一下子修改了一大串的值,类型就是MLOG_WRITE_STRING,就是代表你一下子在那个数据页的某 个偏移量的位置插入或者修改了一大串的值。 所以其实一条redo log看起来大致的结构如下所示: 日志类型(就是类似MLOG_1BYTE之类的),表空间ID,数据页号,数据页中的偏移量,具体修改的数据 大致就是一条redo log中依次排列上述的一些东西,这条redo log表达的语义就很明确了,他的类型是什么,类型就 告诉了你他这次增删改操作修改了多少字节的数据; 然后在哪个表空间里操作的,这个就是跟你SQL在哪个表里执行的是对应的;接着就是在这个表空间的哪个数据页里 执行的,在数据页的哪个偏移量开始执行的,具体更新的数据是哪些呢。 有了上述信息,就可以精准完美的还原出来一次数据增删改操作做的变动了。 只不过如果是MLOG_WRITE_STRING类型的日志,因为不知道具体修改了多少字节的数据,所以其实会多一个修改数 据长度,就告诉你他这次修改了多少字节的数据,如下所示他的格式: 日志类型(就是类似MLOG_1BYTE之类的),表空间ID,数据页号,数据页中的偏移量,修改数据长度,具体修改的数据 因此今天就简单给大家讲解一下redo log的日志的格式,其实没大家想的那么复杂,当然如果往深了说,那可能也比你想 的复杂很多,比如redo log日志里面可能会记录你更新了哪些索引之类的,那就复杂了去了,但是这些东西就等我们讲到索引那块的时 候再说好了! 现在大家对redo log日志的格式了解到这个程度其实就可以了,你脑子里应该更加清晰了一些,就是执行增删改的时 候,在Buffer Pool里通过复杂的缓存页机制完成更新

3、 redo log block和redo log的关系

平时我们执行CRUD的时候,从磁盘加载数据页到buffer pool的缓存页里去,然后对缓存页执行增删改,同时还会写 redo log到日志文件里去,后续不定时把缓存页刷回磁盘文件里去,大概就是这个原理

那么上次我们也介绍了一下每一条redo log长什么样子,说白了,他就是记录了: 表空间号+数据页号+数据页内偏移量+修改了几个字节的数据+实际修改数据 就是简简单单这么一条日志罢了 所以大家可以想一下,redo log就是按照上述格式,一条一条的直接就写入到磁盘上的日志文件里去了吗? 显然不是的! 其实MySQL内有另外一个数据结构,叫做redo log block,大概你可以理解为,平时我们的数据不是存放在数据页了 的么,用一页一页的数据页来存放数据。 那么对于redo log也不是单行单行的写入日志文件的,他是用一个redo log block来存放多个单行日志的。 一个redo log block是512字节,这个redo log block的512字节分为3个部分,一个是12字节的header块头,一个是 496字节的body块体,一个是4字节的trailer块尾

在这里面,12字节的header头又分为了4个部分。 包括4个字节的block no,就是块唯一编号; 2个字节的data length,就是block里写入了多少字节数据; 2个字节的first record group。这个是说每个事务都会有多个redo log,是一个redo log group,即一组redo log。那么在这个block里的第一组redo log的偏移量,就是这2个字节存储的; 4个字节的checkpoint on

所以我们看到上图就知道,其实对于我们的redo log而言,他确实是不停的追加写入到redo log磁盘文件里去的,但 是其实每一个redo log都是写入到文件里的一个redo log block里去的,一个block最多放496自己的redo log日志。 此时可能有人会有疑问了,到底一个一个的redo log block在日志文件里是怎么存在的?那么一条一条的redo log又 是如何写入日志文件里的redo log block里去的呢?估计很多人都很奇怪这个问题。 所以我们接下来就给大家解答这个问题。 大家先想一下,假设你有一个redo log日志文件,平时我们往里面写数据,你大致可以认为是从第一行开始,从左往 右写,可能会有很多行,

好,那么所以现在既然如此,假设你要写第一个redo log了,是不是应该起码是先在内存里把这个redo log给弄到一 个redo log block数据结构里去? 然后似乎你应该是等内存里的一个redo log block的512字节都满了,再一次性把这个redo log block写入磁盘文件?

然后其实按照我们所说的,一个redo log block就是512字节,那么是不是真正写入的时候,把这个redo log block的 512字节的数据,就写入到redo log文件里去就可以了?那么redo log文件里就多了一个block

对于这个所谓的redo log和redo log block的关系,以及redo log block如何进入日志 文件,日志文件里是如何存放一个又一个的redo log block的,应该都很清楚了! 其实有一定开发经验的朋友都知道,写文件的时候,可以按照字节,一个字节一个字节的写入的,文件里存放的东西 就是很多很多字节,依次排开,然后其中可能512个字节组合起来,就固定代表了一个redo log block。 这其实就是任何一个中间件系统,数据库系统,底层依赖磁盘文件存储数据的一个共同的原理,所以大家也不用把这 个复杂数据写入磁盘文件想象的太复杂了。 那么如果依次在磁盘文件里的末尾追加不停的写字节数据,就是磁盘顺序写;但是假设现在磁盘文件里已经有很多很 多的redo log block了,此时要在磁盘里某个随机位置找到一个redo log block去修改他里面几个字节的数据,这就 是磁盘随机写

4、直接强行把redo log写入磁盘?非也,揭秘redo log buffer!

这个redo log到底是如何通过内存缓冲之后,再进入磁盘文件里去的,这就涉及到了一个新的组件, redo log buffer,他就是MySQL专门设计了用来缓冲redo log写入的。

这个redo log buffer其实就是MySQL在启动的时候,就跟操作系统申请的一块连续内存空间,大概可以认为相当于是buffer pool吧。那个buffer pool是申请之后划分了N多个空的缓存页和一些链表结构,让你把磁盘上的数据页加载到内存里来的。 redo log buffer也是类似的,他是申请出来的一片连续内存,然后里面划分出了N多个空的redo log block

通过设置mysql的innodb_log_buffer_size可以指定这个redo log buffer的大小,默认的值就是16MB,其实已经够大了,毕 竟一个redo log block才512自己而已,每一条redo log其实也就几个字节到几十个字节罢了。

所以大家看到这里就明白了,上一讲我们就说了,其实redo log都是先写入内存里的redo log block数据结构里去的,然后完 事儿了才会把redo log block写入到磁盘文件里去的 , 这里我们看到了redo log buffer的结构,就很清晰的知道,当你要写一条redo log的时候,就会先从第一个redo log block开 始写入

写满了一个redo log block,就会继续写下一个redo log block,以此类推,直到所有的redo log block都写满。 那么此时肯定有人会问了,万一要是redo log buffer里所有的redo log block都写满了呢? 那此时必然会强制把redo log block刷入到磁盘中去的! 我们上一次讲到了redo log block刷入磁盘文件中的示意,其实就是把512字节的redo log block追加到redo log日志文件里 去就可以了

另外还要给大家讲一点的是,其实在我们平时执行一个事务的过程中,每个事务会有多个增删改操作,那么就会有多个redo log,这多个redo log就是一组redo log,其实每次一组redo log都是先在别的地方暂存,然后都执行完了,再把一组redo log给写入到redo log buffer的block里去的。 如果一组redo log实在是太多了,那么就可能会存放在两个redo log block中 ,但是反之,如果说一个redo log group比较小,那么也可能多个redo log group是在一个redo log block里的

5、 redo log buffer中的缓冲日志,到底什么时候可以写入磁盘?

首先,我们先来看看redo log block是哪些时候会刷入到磁盘文件里去:

  1. 如果写入redo log buffer的日志已经占据了redo log buffer总容量的一半了,也就是超过了8MB 的redo log在缓冲里了,此时就会把他们刷入到磁盘文件里去
  2. 一个事务提交的时候,必须把他的那些redo log所在的redo log block都刷入到磁盘文件里去,只 有这样,当事务提交之后,他修改的数据绝对不会丢失,因为redo log里有重做日志,随时可以恢复事 务做的修改
  3. 后台线程定时刷新,有一个后台线程每隔1秒就会把redo log buffer里的redo log block刷到磁盘 文件里去
  4. MySQL关闭的时候,redo log block都会刷入到磁盘里去

    忽略上面的第四条不说,因为关闭MySQL的时候必然会刷redo log到磁盘,其他三条其实我们都看到 了,也就是说,如果你瞬间执行大量的高并发的SQL语句,1秒内就产生了超过8MB的redo log,此时 占据了redo log buffer一半的空间了,必然会直接把你的redo log刷入磁盘里去

    上面这种redo log刷盘的情况,在MySQL承载高并发请求的时候比较常见,比如每秒执行上万个增删改 SQL语句,每个SQL产生的redo log假设有几百个字节,此时却是会在瞬间生成超过8MB的redo log日 志,必然会触发立马刷新redo log到磁盘。

    其次,第二种情况,其实就是平时执行一个事务,这个事务一般都是在几十毫秒到几百毫秒执行完毕 的,说实在的,一般正常性能情况下,MySQL单事务性能一般不会超过1秒,否则数据库操作就太慢 了。

    那么如果在几十毫秒,或者几百毫秒的时候,执行完毕了一个事务,此时必然会立马把这个事务的redo log都刷入磁盘

    第一种情况其实是不常见的,第二种情况是比较常见的,往往redo log刷盘都是以一个短事务提交时候 发生的,第三种情况就是后台线程每秒自动刷新redo log到磁盘去,这个就是说假设没有别的情况触 发,后台线程自己都会不停的刷新redo log到磁盘。

    但是不管怎么说,主要是保证一个事务执行的时候,redo log都进入redo log buffer,提交事务的时 候,事务对应的redo log必须是刷入磁盘文件,接着才算是事务提交成功,否则事务提交就是失败,保 证这一点,就能确保事务提交之后,数据不会丢,有redo log在磁盘里就行了。

    当然,绝对保证数据不丢,还得配置一个参数,提交事务把redo log刷入磁盘文件的os cache之后,还 得强行从os cache刷入物理磁盘。

    最后给大家说一下redo log日志文件的问题,我们都知道平时不停的执行增删改,那么MySQL会不停的 产生大量的redo log写入日志文件,那么日志文件就用一个写入全部的redo log?对磁盘占用空间越来 越大怎么办?

    别担心,这些问题都可以解决,实际上默认情况下,redo log都会写入一个目录中的文件里,这个目录 可以通过show variables like ‘datadir’来查看,可以通过innodb_log_group_home_dir参数来设置这个 目录的。

    然后redo log是有多个的,写满了一个就会写下一个redo log,而且可以限制redo log文件的数量,通 过innodb_log_file_size可以指定每个redo log文件的大小,默认是48MB,通过 innodb_log_files_in_group可以指定日志文件的数量,默认就2个。

    所以默认情况下,目录里就两个日志文件,分别为ib_logfile0和ib_logfile1,每个48MB,最多就这2个 日志文件,就是先写第一个,写满了写第二个。那么如果第二个也写满了呢?别担心,继续写第一个, 覆盖第一个日志文件里原来的redo log就可以了。

    所以最多这个redo log,mysql就给你保留了最近的96MB的redo log而已,不过这其实已经很多了,毕 竟redo log真的很小,一条通常就几个字节到几十个字节不等,96MB足够你存储上百万条redo log 了!

    如果你还想保留更多的redo log,其实调节上述两个参数就可以了,比如每个redo log文件是96MB, 最多保留100个redo log文件。