本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
前言
上篇文章,MySQL系列P4—binlog简介,简单介绍了binlog。今天我们来看MySQL中另一个重要的日志——redolog。
redolog
redolog是InnoDB引擎专有的日志系统。主要是用来实现事务的持久性以及实现crash-safe功能。redolog属于物理日志,记录的是sql语句执行之后数据页上的具体修改内容。
redolog的作用
我们都知道,当MySQL运行的时候,会将数据从磁盘中加载到内存当中。当执行sql语句对数据进行修改时,修改后的内容其实都只是暂时保存到内存当中,如果此时断电或者出现其他情况导致机器宕机,这些修改就会丢失。因而,当修改完数据之后,MySQL会寻找机会将这些内存中的记录刷回到磁盘当中。但这就出现一个性能问题,主要有两个方面:
InnoDB中是以页为数据单位与磁盘进行交互的,而一个事务很可能只是修改了一个页上的几个字节,如果将一个完整的数据页刷回磁盘当中,浪费资源;- 一个事务可能涉及到多个数据页,这些数据页只是逻辑上连续,在物理上并不连续,使用随机
IO性能太差;
因此,MySQL设计了redolog来记录事务对数据页具体做了哪些修改,之后将redolog再刷回磁盘当中。你可能会有疑惑,本来就是想减少io,这不又加上一次io么?InnoDB的设计者在设计之初就已经考虑到了这些。redolog文件一般都比较小,且在刷回磁盘的过程中是顺序io,相比于**随机io**来说,性能更好。
redolog结构
redolog由两部分组成:
- 一个是内存中的日志缓存
redo log buffer - 一个是磁盘中的日志文件
redo log file
当每次对数据记录进行修改的时候,都会将这些修改内容先写入redo log buffer中,后续等待合适的时机将内存中的修改刷回到redo log file中。这种先写日志,再写磁盘的技术就是WAL(Write-Ahead Logging)技术。需要注意的是redolog刷回磁盘的时机,它比数据页先刷回磁盘。那么哪些场景会涉及到redolog的使用呢,主要分两类:
- 索引
undo页面的修改。
redolog的整体流程
如图所示,当对数据记录进行修改时,redolog的流程如下:
- 若数据已在内存中则直接进行修改,否则先将数据从磁盘加载到内存中;
- 修改完成之后,生成一条
redolog,将这条redolog写入redo log buffer中,记录的是修改之后的值; - 根据选定的策略,将
redo log file中的内容刷回到redo log file中;
redolog 刷回 redo log file的策略
在计算机操作系统中,用户空间的数据一般无法直接写入到磁盘中,中间必须先经过操作系统内核空间缓冲区,即os buffer。因此,redo log buffer写入redo log file实际上是先写入os buffer中,再通过系统调用fsync()刷回到磁盘中,过程如下:
在my.ini配置文件中,可以通过innodb_flush_log_at_trx_commit参数来配置redo log buffer如何刷回redo log file的策略。
-
0:事务提交后不会立即将
redo log buffer中的日志写入到os buffer,而是每秒将redo log buffer写入到os buffer中,再调用fsync()写入到redo log file中。当MySQL挂了的话,就会丢失1秒钟的数据
-
1:事务提交后都会将
redo log buffer中的日志写入os buffer,再调用fsync刷到redo log file中。这样方式即使系统崩溃也不会丢失任何数据,但由于每次事务提交时都要写入磁盘,性能较差
- 2:事务提交后仅仅将
redo log buffer中的日志写入os buffer,然后每秒调用fsync()将os buffer中的日志写入到redo log file,如果只是MySQL挂了,不会出现数据丢失,但是要是机器宕机则会丢失1秒钟的数据
redo log 格式
redolog采用固定大小,循环写入的格式,当redolog写满之后,会重新从头开始写。为什么这么设计呢?
redo log存在的意义主要就是降低对数据页刷盘的要求。redolog记录了数据页上的修改,但是当数据页也刷回到磁盘后,这些记录就失去作用了。因此当MySQL判断之前的redolog已经失去作用之后,新数据会将这些失效的数据进行覆盖。那如何判断该不该进行覆盖呢?主要根据两个数据,write position和check point,如下所示:
write pos表示redolog当前已写入redolog中的日志序列号LSN(log sequence number)。当数据页也已经刷回磁盘之后,会更新redo log file中的LSN,表示到这个LSN之前的数据已经落盘。在redolog中还有一个特殊的LSN,就是check point。
-
write pos到check point之间的部分是redolog空余的部分,用于记录新的记录 -
check point到write pos之间是redolog已经记录的数据页修改部分,但此时数据页还未刷回磁盘的部分 -
当
write pos追上check point时,会先推动check point向前移动,空出位置再记录新的日志
启动innodb的时候,不管上次是正常关闭还是异常关闭,总是会进行恢复操作。恢复时,会先检查MySQL内存中加载进来的数据页中的LSN,如果这个LSN小于redolog中的write pos位置,说明在redolog上记录着数据页上尚未完成的操作,接着就会从最近的一个check point出发,开始同步数据。
那有没有可能数据页中的LSN大于redolog中的LSN呢?答案是当然可能。出现这种情况时,这时超出redolog的部分将不会重做,因为这本身就表示已经做过的事情,无需再重做。
redolog与binlog区别
| redolog | binlog | |
|---|---|---|
| 文件大小 | redolog的大小是固定的 | binlog可通过参数调整binlog文件的大小 |
| 实现方式 | redolog是InnoDB引擎独有的 | binlog是Server层实现的,所有引擎都可以使用 binlog日志 |
| 记录方式 | redolog采用循环写的方式记录,当写到结尾时,会回到开头循环写日志 | binlog 通过追加的方式记录,当写满时,会新生成一个文件继续写 |
| 适用场景 | redo log适用于崩溃恢复(crash-safe) | binlog适用于主从复制和数据恢复 |
由上面的对比可知,binlog日志只适合用于归档,只依靠binlog是没有crash-safe能力的。但只有redo log也不行,因为redo log是InnoDB特有的,且redolog会被覆盖掉。因此需要binlog和redo log二者同时记录,才能保证当数据库发生宕机重启时,数据不会丢失。
两阶段提交
上面简单介绍了redolog和binlog,在对数据进行修改时,他们都会对这些修改进行保存落地,只是一个是物理日志,一个是逻辑日志。那他俩具体在修改过程中是如何执行的呢?
假设现在有一条update语句要执行,update from table_name set c=c+1 where id=2,执行流程如下:
- 先定位到
id=2这一条记录 - 执行器拿到引擎给的行数据,把这个值加上1,得到新的一行数据,再调用引擎接口写入这行新数据
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到
redolog里面,此时redolog处于prepare状态。然后告知执行器执行完成了,随时可以提交事务; - 执行器生成这个操作的
binlog,并把binlog写入磁盘; - 执行器调用引擎的提交事务接口,引擎把刚刚写入的
redolog修改成提交(commit)状态,更新完成;
示意图如下所示:
这种将redolog的写入拆分成prepare和commit两个步骤的过程称之为两阶段提交。
redolog 和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。如果不使用两阶段提交,而是先写其中一个再写另外一个可能会带来一些问题。
此时还是使用update来举例。假设当前id=2,有一个字段c=0,分别分析以下情况:
先写redolog再写binlog
假设先写redolog,当redolog写完,但是binlog还未写完的时候,此时MySQL突然出现异常导致重启。由于之前redolog已经写完,系统重启后,修改的记录仍然存在,所以恢复后这一行c的值是 1。但由于系统重启,binlog中并未有这条记录。之后备份日志的时候,存起来的binlog里面就没有这条语句。然后你会发现,如果需要用这个binlog来恢复临时库的话,由于这个更新语句的binlog丢失,这个临时库就会少了这一次更新,恢复出来的这一行c的值就是 0,与原库的值不同。
先写binlog再写redolog
假如先写binlog,然后写redolog的时候系统重启。重启之后,redolog中没有对c进行修改的记录,此时c的值还是0。但是 binlog 里面已经记录了把c从0改成1这个日志。所以,在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是 1,与原库的值不同。
因此,如果是先写其中一个日志再写另一个日志的话,就会出现数据库的状态与使用binlog恢复出来的库的状态不一致的情况。