MySQL之redo log

1,110 阅读9分钟

引论

为啥需要redo日志?redo日志类型都有哪些?redo buffer是什么?lsn是什么?checkpoint又是什么?看完本篇文章,你会知道这些概念的来龙去脉。

  • MySQL为了加快数据的检索、修改等操作,向操作系统借用了一些内存,用来与磁盘的数据进行交互。这部分的内存叫做:Buffer Pool

  • 随着程序的运行,对内存中的页进行修改,我们修改之后马上同步到磁盘中吗?并不是,那样1、2、效率低,一个页面可能只改了一个字节,但你想,一个页面可是有16kb的。真实的情况是先只保留内存中的修改,更新一下freeflushlru链表等。如果此时出现断电等突发情况,内存中的东西马上就没了,未更新到磁盘,我们做的修改也没了。那咋办? 天降正义——redo日志

  • redo 日志是为了记录内存中的修改,为了宕机后的从容重启。 redo 日志会把事务在执行过程中对数据库所做的所有修改全部记录下来,在之后如果发生系统崩溃,重启后可以把事务所做的修改全都恢复出来。

redo 日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统因崩溃而重启后可以把事务所做的任何修改都回复过来。

  • redo 日志种类繁多
  1. 比如只记录某页某偏移量出修改了1个字节的redo日志类型
  2. 比如只记录某页某偏移量出修改了2个字节的redo日志类型
  3. redo日志也有组合使用的,比如删除某个区间范围内的记录,使用两个日志,标记删除的开头和结尾。

日志分组-MTR

  • redo 日志需要分组,每一个组中的日志组成了一个原子性的修改,是不可分割的一部分。

首先介绍redo日志分组的理由:

比如,插入一条记录,当出现悲观插入时,也就是目标页空间不够,需要分页,然后将原来页面的部分数据复制到新增页面中,然后插入该行记录,然后在上面的内节点中插入一条目录项记录(如果该目录项记录也不够,需要向上递归插入),然后还得更新系统页面,比如要修改各种段、区的统计信息、修改Buffer Pool中的链表信息等等

下面举例插入一条索引列为10的记录

乐观插入

img

悲观插入

img img

接着上面的话题,如果我的redo日志只记录了分页操作,没有记录在上面的内节点中插入一条目录项记录的操作的话,用这段日志恢复的B+树就是一棵不完整的树,这是不能容忍的。所以redo日志也要满足原子性,那就是分组。

  • 如何分组?在同一组的redo日志最后面加上一个哨兵日志,表示这是一个完整的操作。 img
  • 所有redo日志都得分组吗,哪怕那些只需要一条redo日志的操作?当然不是,redo日志的头部type一个字节,表示redo的类别,实际上后面7个比特已足够表明redo日志的种类,所以可以在第一个bit上做文章(是不是有点像ASCII字符编码,后来产生的使用了第一个bit来做文章的字符编码)。最后的规定为:

如果type字段的第一个bit1,代表这个需要保证原子性的操作只产生了一条单一的redo日志,否则就表示这个需要保证原子性的操作产生了一系列的redo日志。

  • Mini-TransactionMTR:表示对底层页面进行一次原子访问的过程称为一个MTR

目前为止总结一下:

一个事务可以包含若干条语句,每一条语句又可以包含若干个MTR,每一个MTR又可以包含若干条redo日志 img

redo log buffer - 内存

  • log buffer的基本单位:log buffer block,大小为512字节。 img 真正的log存储在 log block body496字节里面。 注意log block头部中用四个字节的checkpoint信息,这是接下来的重点

说到底redo日志也是写到磁盘中的,也是写文件,速度不会有问题吗?所以同样的,来个缓存。

  • buffer pool的作用类似,redo log也有一个缓冲内存,叫做log buffer。一个mtr对应的日志组先加入到log buffer中,然后刷新到磁盘。

  • 顺序写入redo log buffer中,写完一个写下一个。 img

  • 为了知道将redo日志写到log buffer中的具体位置,提供了系统变量buf_free

  • 不是一条日志一条日志的写入到log buffer中,而是一个mtr一个mtr的形式写入到log buffer中。

  • 不同的事务产生的mtr可以交叉写 img

  • log buffer 刷新到磁盘的时机

  1. log buffer 容量不够时
  2. 事务提交时,为什么这时候需要刷新到磁盘,你仔细想一下,就会明白,这就是redo log存在的意义啊。
  3. 后台有一个线程,大约每秒一次的频率将log buffer中的redo日志刷新到磁盘
  4. 正常关闭服务器时
  5. checkinpoint

redo log 日志文件组 - 磁盘

  • 介绍完,redo log在内存中的存储格式——block,那么磁盘中的redo log究竟是啥样的呢?这就是redo log 日志文件组。
SHOW VARIABLES LIKE 'datadir';

3c71d3b7e0003d180d7e04a7530bd590.png

实际的写入是一种循环写的机制,写完ib_logfile0,就写ib_logfile1 img

  • redo log日志文件格式 将log buffer中的redo日志刷新到磁盘的本质就是把block的景象写入到日志文件中。所以就如同buffer pool的页与磁盘中的页一样,都是相同大小。在redo日志文件组中,每个文件的大小都是一样的,格式也一样。 img

LSN - log sequence number

用来记录当前总共已经写入的redo日志量。不是从0开始,而是从8704开始计数。 下面通过日志的写入老看看lsn的值到底是如何改变的

  • 初始化 img 因为一个block的头占用12字节
  • 插入一条200字节的mtr img
  • 插入一条1000字节的mtr img 总结:每个mtr开始处都有对应的lsn值,比如mtr1对应的是8716mtr2对应的是8916,等等。lsn值越小说明越早写入redo日志中。

flushed_to_disk_lsn - 将buffer中的日志刷新到磁盘中

redo buffer中已经有一个全局变量buf_free用来记录下次插入redo 日志到buffer中,应该插入到哪里。实际上还需要一个全局变量用来记录存储在buffer中的redo日志哪些已经被刷新到磁盘的日志文件组中,这个全局变量就是buf_next_to_write.

img

  • 就像两个地址buf_next_to_writebuf_free一样, 有相对应的两个变量flushed_to_disk_lsnlsn,初始都是8704,但是随着系统的运行,不断有redo日志生成,但是不会生成一个redo日志就会将该redo日志刷新到磁盘中,所以flushed_to_disk_lsn渐渐的就与buf_freelsn拉开了差距。
地址指针buf_freebuf_next_to_write
变量名称lsnflushed_to_disk_lsn
buf_free:redo日志该往redo buffer中的哪个位置写了
buf_next_to_writeredo buffer中哪些日志已经写到redo log日志文件中去了,该哪些redo log日志文件写到磁盘的日志文件中去了。

万事俱备,只欠东风

出来吧 flush链表

  • flush链表中的脏页按照第一次修改发生的时间顺序进行排列,也就是按照oldest_modification代表的lsn值进行排序。这个lsn是首次修改相应页面的mtr开始时的lsn
  • 被多次更新的页面不会发生flush链表节点的多次排序,只是会更新每个节点——也就是每个控制块的newest_modificationlsn值。这个lsn值是这个页面最近一次修改的mtr结束时的lsn值。

出来吧 checkpoint

日志文件组大小是有限的,所以采用循环写,如此一来的问题:新写入的redo日志会不会与最开始的redo日志进行追尾呢?什么时候可以覆盖呢?

  • 判断某些redo日志占用的磁盘空间是否可以覆盖的依据,就是它对应的脏页是否已经被刷新到磁盘中。 举例 img 目前的情况是mtr_1/mtr_2 redo 日志组已经写到磁盘上,但是对应的脏页还在内存中,何以见得?因为flush链表中还存在mtr_1/mtr_2这两个所对应的控制块啊。 img 页a已经被刷新到磁盘,那么mtr_1日志组也就没用了,就可以被覆盖了。

checkpoint_lsn

这一节变量超多,这是最后的了,checkpoint_lsn代表当前系统可以被覆盖的日志量为多少,它初始值也为8704.

如何做一次checkpoint?

本质上就是增加checkpoint_lsn的操作

  1. 计算当前系统可以被覆盖的最大lsn值。 这个从哪里找?flush链表中找!flush链表尾节点的oldest_modification代表的lsn值就是当前系统可以被覆盖的日志最大的lsn值。因为之前的redo日志记录的对脏页的修改,脏页本身已经刷新到磁盘中去了。redo日志也就没用了。
  2. 将信息写到日志管理文件中。 img 记录完checkpoint信息之后,redo日志文件组个lsn值的关系如下。

总结

  • 1、在MySQL中,如果每次增删改都落盘,那想必性能会很受影响。但是不落盘如何保证事务提交后保持可靠性,对,这就引出来redo log。redo log日志也是要落盘,他快就快在是顺序写。

  • 2、redo log 落盘之间还有一个redo log buffer

  • 3、文章最后部分出现很多个lsn,不要乱:其实checkpoint_lsn、flushed_to_disk_lsn、lsn这三个都是同一起点出发——8704.然后lsn跑的最快,flushed_to_disk_lsn紧随其后,其实就是checkpoint_lsn吭哧吭哧的追,落后的太远,还要前两者等等——系统先不接受客户端的请求,自己忙不过来了,先checkpoint,给之后的redo日志留下足够的空间。

引用来源

1、掘金小册《MySQL是如何运行的》

2、MySQL官方手册