redo log

notion image
 
redo log是一种基于磁盘的数据结构,在崩溃恢复期间用于纠正不完整事务写入的数据。
在正常操作期间,redo log对SQL语句或低级API调用产生的更改表数据的请求进行编码。在意外关闭之前未完成数据文件更新的修改将在初始化期间和接受连接之前自动重做。
默认情况下,redo log在磁盘上由两个名为ib logfile0ib logfile1的文件物理表示。MySQL以循环方式写入重做日志文件。redo log中的数据按照受影响的记录进行编码;这些数据统称为重做。通过重做日志的数据通过不断增加的LSN值表示。

WAL机制

如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程IO成本、查找成本都很高。为了解决这个问题,MySQL使用了WAL技术,WAL的全称是Write-Ahead Logging,它的关键点就是先写日志,再写磁盘
具体来说,当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log里面,并更新内存,这个时候更新就算完成了。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做
 
InnoDB的redo log是固定大小的,比如可以配置为一组4个文件,每个文件的大小是1GB,那么redo log总共就可以记录4GB的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。
notion image
write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。checkpoint是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件
write poscheckpoint之间的是redo log空余的空间,可以用来记录新的操作。如果write pos追上checkpoint,表示redolog满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把checkpoint推进一下。
有了redo logInnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe

redo log 内容

redo log里本质上记录的就是在对某个表空间的某个数据页的某个偏移量的地方修改了几个字节的值,具体修改的值是什么,
redo log划分为了不同的类型,MLOG_1BYTE类型的日志指的就是修改了1个字节的值,MLOG_2BYTE类型的日志指的就是修改了2个字节的值,以此类推,还有修改了4个字节的值的日志类型,修改了8个字节的值的日志类型。当然,如果你要是一下子修改了一大串的值,类型就是MLOG_WRITE_STRING,就是代表你一下子在那个数据页的某个偏移量的位置插入或者修改了一大串的值。
一条redo log看起来大致的结构如下所示:
日志类型(就是类似MLOG_1BYTE之类的),表空间ID,数据页号,数据页中的偏移量,具体修改的数据

redolog的写入机制

redo log block

MySQL内有一个数据结构,叫做redo log block 用来存放多个单行日志
一个redo log block是512字节,这个redo log block的512字节分为3个部分,一个是12字节的header块头,一个是496字节的body块体,一个是4字节的trailer块尾
notion image
在这里面,12字节的header头又分为了4个部分。
  1. 包括4个字节的block no,就是块唯一编号;
  1. 2个字节的data length,就是block里写入了多少字节数据;
  1. 2个字节的first record group。这个是说每个事务都会有多个redo log,是一个redo log group,即一组redo log。那么在这个block里的第一组redo log的偏移量,就是这2个字节存储的;
  1. 4个字节的checkpoint on
 
redo log时,先等 redo log block 的512字节都满了,再一次性把这个block写入磁盘
如果依次在磁盘文件里的末尾追加不停的写字节数据,就是磁盘顺序写;但是假设现在磁盘文件里已经有很多很多的redo log block了,此时要在磁盘里某个随机位置找到一个redo log block去修改他里面几个字节的数据,这就是磁盘随机写,看下图
notion image

redo log buffer

redo log buffer是MySQL在启动的时候,就跟操作系统申请的一块连续内存空间,里面划分出了N多个空的redo log block,通过设置mysql的innodb_log_buffer_size可以指定这个redo log buffer的大小,默认的值就是16MB
 
事务在执行过程中,生成的redo log是要先写到redo log buffer的。redo log buffer里面的内容,不需要每次生成后都直接持久化到磁盘。如果事务执行期间MySQL发生异常重启,那这部分日志就丢了。由于事务并没有提交,所以这时日志丢了也不会有损失。
 
事务还没提交的时候,redo log buffer中的部分日志有可能被持久化到磁盘。
这个问题,要从redo log可能存在的三种状态说起。这三种状态,对应的就是下图的三个颜色块。
notion image
这三种状态分别是:
  1. 存在redo log buffer中,物理上是在MySQL进程内存中,就是图中的红色部分;
  1. 写到文件系统(write),但是没有持久化(fsync),物理上是在文件系统的page cache里面,也就是图中的黄色部分;
  1. 持久化到磁盘,对应的是hard disk,也就是图中的绿色部分。
日志写到redo log buffer是很快的,wirte到page cache也差不多,但是持久化到磁盘的速度就慢多了

写入策略参数

 
为了控制redo log的写入策略,InnoDB提供了innodb_flush_log_at_trx_commit参数,它有三种可能取值:
  1. 设置为0的时候,表示每次事务提交时都只是把redo log留在redo log buffer中;
  1. 设置为1的时候,表示每次事务提交时都将redo log直接持久化到磁盘;
  1. 设置为2的时候,表示每次事务提交时都只是把redo log写到page cache
 
InnoDB有一个后台线程,每隔1秒,就会把redo log buffer中的日志,调用write写到文件系统的page cache,然后调用fsync持久化到磁盘。
意,事务执行中间过程的redo log也是直接写在redo log buffer中的,这些redo log也会被后台线程一起持久化到磁盘。
也就是说,一个没有提交的事务的redo log,也是可能已经持久化到磁盘的。
 
实际上,除了后台线程每秒一次的轮询操作外,还有两种场景会让一个没有提交的事务的redo log写入到磁盘中。
  1. 一种是,redo log buffer占用的空间即将达到 innodb_log_buffer_size一半的时候,后台线程会主动写盘。注意,由于这个事务并没有提交,所以这个写盘动作只是write,而没有调用fsync,也就是只留在了文件系统的page cache
  1. 另一种是,并行的事务提交的时候,顺带将这个事务的redo log buffer持久化到磁盘。
    1. 假设一个事务A执行到一半,已经写了一些redo logredo log buffer中,这时候有另外一个线程的事务B提交,如果innodb_flush_log_at_trx_commit设置的是1,那么按照这个参数的逻辑,事务B要把redo log buffer里的日志全部持久化到磁盘。这时候,就会带上事务A在redo log buffer里的日志一起持久化到磁盘。
 
这里需要说明的是,redo log 和 binlog写入过程中,时序上redo log先prepare, 再写binlog,最后再把redo log commit
如果把innodb_flush_log_at_trx_commit设置成1,那么redo log在prepare阶段就要持久化一次,
因为有一个崩溃恢复逻辑是要依赖于prepare 的redo log,再加上binlog来恢复的
 
每秒一次后台轮询刷盘,再加上崩溃恢复这个逻辑,InnoDB就认为redo log在commit的时候就不需要fsync了,只会write到文件系统的page cache中就够了。

双一设置

通常我们说MySQL的“双1”配置,指的就是sync_binloginnodb_flush_log_at_trx_commit都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是redo log(prepare 阶段),一次是binlog。
 

redo log 和 binlog怎么关联起来的?

它们有一个共同的数据字段,叫XID。崩溃恢复的时候,会按顺序扫描redo log:
  • 如果碰到既有prepare、又有commit的redo log,就直接提交;
  • 如果碰到只有parepare、而没有commit的redo log,就拿着XID去binlog找对应的事务。

redo log一般设置多大?

回答:redo log太小的话,会导致很快就被写满,然后不得不强行刷redo log,这样WAL机制的能力就发挥不出来了。
所以,如果是现在常见的几个TB的磁盘的话,就不要太小气了,直接将redo log设置为4个文件、每个文件1GB吧。