MySQL - redo log
出现原因
redo log 重做日志,是属于InnoDB特有的,为了解决InnoDB在存在脏页时,出现故障造成数据丢失问题而设计的。
InnoDB的修改数据的基本流程如下:
当我们想要修改DB上某一行数据的时候,InnoDB是把数据从磁盘读取到内存的缓冲池上进行修改。这个时候数据在内存中被修改,与磁盘中相比就存在了差异,称这种有差异的数据为脏页。
InnoDB对脏页的处理不是每次生成脏页就将脏页刷新回磁盘,这样会产生海量的IO操作,严重影响InnoDB的处理性能。对此,InnoDB有一套完善的处理策略。
既然脏页与磁盘中的数据存在差异,那么如果这期间DB出现故障就会造成数据的丢失。
为了解决这个问题,redo log就应运而生了。
日志类型
redo log在数据库重启恢复的时候被使用,因为其属于物理日志的特性,恢复速度远快于逻辑日志。而我们经常使用的binlog就属于典型的逻辑日志。
工作原理
redo log就是存储了数据被修改后的值。当我们提交一个事务时,InnoDB会先去把要修改的数据写入redo log,然后再去修改缓冲池里面的真正数据页。
当有一条记录需要更新的时候:
- InnoDB 引擎就会先把记录写到 redo log buffer;
- 并更新内存中数据页,这个时候更新就算完成了,然后返回新数据到客户端;
- InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,即redo log file。
redo log本身也由两部分所构成即重做日志缓冲(redo log buffer)和重做日志文件(redo log file)。这样的设计同样也是为了调和内存与磁盘的速度差异。
上述步骤中,第1步就是写到redo log buffer,第3步是写到redo log file。
先写日志,再写磁盘,就是WAL(Write-ahead Logging)机制。
binlog 和 redo log的写入磁盘都是WAL机制,而且是按顺序写入磁盘,将随机写变成顺序写。
即执行修改数据时,是用顺序写的redo log 代替 随机写的到磁盘中寻找并修改页数据。
像写日志这种顺序写,每秒几万次没问题。
至于内存脏页什么时候才真正持久化到硬盘,就涉及脏页刷新策略。
以下四种情况,InnoDB 会进行脏页刷新:
MySQL 认为系统空闲;
MySQL 正常关闭过程;
Free List 不足;
redo log 写满。
InnoDB将redo log buffer写入到redo log file的策略可以通过innodb_flush_log_at_trx_commit这个参数来控制。
redo log 用于保证 crash-safe 能力,参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。建议设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。
为0时性能较好,但是会丢失掉master thread还没刷新进磁盘部分的数据。
简单介绍一下master thread,这是InnoDB一个在后台运行的主线程,从名字就能看出这个线程相当的重要。它做的主要工作包括但不限于:刷新日志缓冲,合并插入缓冲,刷新脏页等。master thread大致分为每秒运行一次的操作和每10秒运行一次的操作。master thread中刷新数据,属于checkpoint的一种。所以如果在master thread在刷新日志的间隙,DB出现故障那么将丢失掉这部分数据。
当该值为2时,当DB发生故障能恢复数据。但如果操作系统也出现宕机,那么就会丢失掉,文件系统没有及时写入磁盘的数据。
这里说明一下,innodb_flush_log_at_trx_commit设为非0的值,并不是说不会在master thread中刷新日志了。master thread刷新日志是在不断进行的,所以redo log写入磁盘是在持续的写入。
redo log结构
InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。
write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。
checkpoint 是当前要把记录更新到文件的位置,也是往后推移并且循环的。
write pos 和 checkpoint 之间的是还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示日志满了,这时候不能再执行新的更新,得停下来先清掉一些记录,把 checkpoint 推进一下。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。
Q & A
Q:redo log 一般设置多大?
A:redo log 太小的话,会导致很快就被写满,然后不得不强行刷 redo log,这样 WAL 机制的能力就发挥不出来了。
所以,如果是现在常见的几个 TB 的磁盘的话,直接将 redo log 设置为 4 个文件、每个文件 1GB 吧。
Q:正常运行中的实例,数据写入后的最终落盘,是从 redo log 更新过来的还是从 buffer pool 更新过来的呢?
A:这里涉及到了redo log 里面到底是什么的问题。实际上,redo log 并没有记录数据页的完整数据,所以它并没有能力自己去更新磁盘数据页,也就不存在数据最终落盘,是由 redo log 更新过去的情况。
如果是正常运行的实例的话,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写盘。这个过程,甚至与 redo log 毫无关系。
在崩溃恢复场景中,InnoDB 如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让 redo log 更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态。
Q:数据写入redo log buffer 和 redo log file顺序
A:在一个事务的更新过程中,日志是要写多次的。比如下面这个事务:
begin;i
nsert into t1 ...
insert into t2 ...
commit
这个事务要往两个表中插入记录,插入数据的过程中,生成的日志都得先保存起来,但又不能在还没 commit 的时候就直接写到 redo log 文件里。
所以,redo log buffer 就是一块内存,用来先存 redo 日志的。
也就是说,在执行第一个 insert 的时候,数据的内存被修改了,redo log buffer 也写入了日志。
但是,真正把日志写到 redo log 文件(文件名是 ib_logfile+ 数字),是在执行 commit 语句的时候做的。