redo log
是一种基于磁盘的数据结构,在崩溃恢复期间用于纠正不完整事务写入的数据。在正常操作期间,
redo log
对SQL语句或低级API调用产生的更改表数据的请求进行编码。在意外关闭之前未完成数据文件更新的修改将在初始化期间和接受连接之前自动重做。默认情况下,
redo log
在磁盘上由两个名为ib logfile0
和ib 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的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。write pos
是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。checkpoint
是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。write pos
和checkpoint
之间的是redo log
空余的空间,可以用来记录新的操作。如果write pos
追上checkpoint
,表示redolog满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把checkpoint
推进一下。有了
redo log
,InnoDB
就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为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块尾在这里面,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 block
的512字节都满了,再一次性把这个block写入磁盘如果依次在磁盘文件里的末尾追加不停的写字节数据,就是磁盘顺序写;但是假设现在磁盘文件里已经有很多很多的redo log block了,此时要在磁盘里某个随机位置找到一个
redo log block
去修改他里面几个字节的数据,这就是磁盘随机写,看下图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
可能存在的三种状态说起。这三种状态,对应的就是下图的三个颜色块。这三种状态分别是:
- 存在
redo log buffer
中,物理上是在MySQL进程内存中,就是图中的红色部分;
- 写到文件系统(write),但是没有持久化(fsync),物理上是在文件系统的
page cache
里面,也就是图中的黄色部分;
- 持久化到磁盘,对应的是
hard disk
,也就是图中的绿色部分。
日志写到
redo log buffer
是很快的,wirte到page cache
也差不多,但是持久化到磁盘的速度就慢多了写入策略参数
为了控制redo log的写入策略,InnoDB提供了
innodb_flush_log_at_trx_commit
参数,它有三种可能取值:- 设置为0的时候,表示每次事务提交时都只是把
redo log
留在redo log buffer
中;
- 设置为1的时候,表示每次事务提交时都将
redo log
直接持久化到磁盘;
- 设置为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写入到磁盘中。
- 一种是,
redo log buffer
占用的空间即将达到innodb_log_buffer_size
一半的时候,后台线程会主动写盘。注意,由于这个事务并没有提交,所以这个写盘动作只是write,而没有调用fsync,也就是只留在了文件系统的page cache
。
- 另一种是,并行的事务提交的时候,顺带将这个事务的
redo log buffer
持久化到磁盘。
假设一个事务A执行到一半,已经写了一些
redo log
到redo 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_binlog
和innodb_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吧。