Mysql日志---redo

Posted 热爱编程的大忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql日志---redo相关的知识,希望对你有一定的参考价值。

mysql日志---redo


前言

前面的文章中,我们依次介绍了MySQL的slow query log、binlog、relaylog,如果对这三种log仍然有疑问的话,可以向前翻一翻文章查看。今天我们来聊一聊MySQL中的另外一个重要的日志,它就是redolog。

什么是redolog

redolog是InnoDB存储引擎所特有的一种日志,其他存储引擎没有这个日志功能,例如像之前我们用过的MyISAM存储引擎就没有这种redolog的功能。我们知道MySQL从宏观上看,可以分为两次:server服务层和store engine存储引擎层。而我们的redolog就是属于存储引擎层特有的日志。像binlog、relaylog他们是属于服务层日志,所有的存储引擎都会生成这种日志,并且这种日志是先于redolog之前写的,写成功binlog之后才会写redolog。


reldolog的作用

redolog是用来做崩溃恢复使用的,这种崩溃恢复不需要我们人为的参与,MySQL自己内部自己实现了这种崩溃恢复的功能,我们只管享受这种功能给我们带来的服务即可,这种服务给我们的感受就是:MySQL数据库异常宕机的时候,重启服务之后,数据库中之前提交的记录都不会丢失数据仍然可以正常恢复,不管这种提交的记录是否已经更新到具体的表所对应的磁盘page页中。

那么MySQL内部在实现崩溃恢复的功能时,到底是如何实现的呢? 举例来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到redo log里面(redolog日志是循环追加写的,属于顺序IO,记录的速度在某些程度上可以和内存相媲美),并更新内存,这个时候更新就算完成了。MySQL在崩溃恢复的时候,会从记下来的redolog中找到已经提交的更改内容,所以不会担心MySQL异常重启后,数据的丢失。

InnoDB 引擎会在适当的时候,将这redolog中记录的操作更新到表中数据对应的page页所在的物理磁盘上,而这个更新往往是在MySQL服务比较空闲的时候去刷新到磁盘,此时才是真正的把更新的数据内容刷新到对应的page页中。而这个更新redolog日志中的内容到真正的表数据对应的page页的刷盘操作通常比费时的,需要从磁盘中找到对应的page页,还涉及到分页或合并的各种操作,属于随机IO写入性能太差,所以MySQL在执行更新操作的时候,并没有直接去更新真正的数据页中的内容,而只是更新了缓存和记录了redolog日志。先写日志,再写磁盘,这就是很多软件在提高写的性能的时候所使用的WAL(write ahead logging)预写日志的功能。

MySQL的这种崩溃恢复的功能,这就是我们经常所说的crash-safe,而实现这个crash-safe功能的主要组件就是redolog。因为只有innodb存储引擎才有这个特有的日志,所有只有innodb才支持这种崩溃恢复数据不丢失的特性,这也是为什么innodb存储引擎替代myiasm存储引擎成为主流默认的存储引擎的原因之一。

在最早的时候MySQL里并没有InnoDB引擎。MySQL自带的引擎是MyISAM,但是MyISAM没有crash-safe的能力,binlog日志只能用于归档。而InnoDB是另一个公司以插件形式引入MySQL的,既然只依靠binlog是没有crash-safe能力的,所以InnoDB使用另外一套日志系统,也就是redo log来实现crash-safe能力。这就是为什么会有relaylog日志产生的原因。


物理日志VS逻辑日志

redolog从另外一个维度上来讲,它属于一种物理日志。

那么什么是物理日志呢?物理日志记录的是每一个page页中具体存储的值是多少,在这个数据页上做了什么修改。与物理日志对应的是逻辑日志,逻辑日志是记录的每一个page页面中具体数据是怎么变动的,它会记录一个变动的过程,比如把ID=3的行的age字段改为``30。像undolog、binlog、relaylog`这些日志,它们就属于逻辑日志,记录的是数据变化的一个过程、或SQL语句的逻辑。这样可能比较抽象,不容易理解,我们下面举例说明一下。

例如我们把一个page页中的一个数据从1改为2,再从2改为3,再从3改为4,再从4改为5。这是这个数据在page页中变换的过程。在物理日志中,它只会记录最后的一个值5,表示这个page页中的数据的值为5。而逻辑日志会记录1->2,2->3,3->4,4->5这个数据变化的过程。


redolog的组成

只要是使用的innodb存储引擎,那么redolog就一定是存在的。它们在磁盘上会落实成固定个数固定大小的日志文件。一般有2个或4个,这可以在初始化MySQL实例的时候,可以进行配置。如下是我自己的一个MySQL实例的redolog配置,配置了2个redolog日志文件:

root@test:/var/lib/mysql# pwd
/var/lib/mysql
root@test:/var/lib/mysql# ls -lstr ib_logfile*
49152 -rw-r----- 1 mysql mysql 50331648 Dec 28 15:45 ib_logfile1
49152 -rw-r----- 1 mysql mysql 50331648 Jan  3 12:53 ib_logfile0
root@test:/var/lib/mysql# du -sh ib_logfile*
48M	ib_logfile0
48M	ib_logfile1
root@test:/var/lib/mysql#

决定redolog日志文件的个数的参数是innodb_log_files_in_group,日志文件的个数建议设置为3或4。决定每一个日志文件大小的值为innodb_log_file_size,单个日志文件最大值建议设置为1GB,如下是查看我自己的一个MySQL示例的redolog的参数配置信息:

mysql> show variables like 'innodb_log_files_in_group';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| innodb_log_files_in_group | 2     |
+---------------------------+-------+
1 row in set (0.00 sec)

mysql> show variables like 'innodb_log_file_size';
+----------------------+----------+
| Variable_name        | Value    |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+
1 row in set (0.00 sec)

mysql> select 50331648/1024/1024;
+--------------------+
| 50331648/1024/1024 |
+--------------------+
|        48.00000000 |
+--------------------+
1 row in set (0.00 sec)

注意:如果要修改这个参数的值,需要停止MySQL服务才可以。一般在初始化完成之后,不建议修改这个值的大小。

redo log是循环写的,空间固定会用完。它一般是由2个或4个日志文件组成,当写完1个后,会写第2个,当第2个页写完后,会再去写第1个,如此往复循环,当然在写覆盖写第1个文件之前,需要把里面的日志内容,刷新到磁盘,然后再去覆盖写。binlog是可以追加写入的。"追加写"是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

redolog循环写的方式向下面这个图片所示,write pos 到 check point 之间的部分是 redo log 空着的部分,用于记录新的记录;check point 到 write pos 之间是 redo log 待落盘的数据页更改记录。当 write pos追上check point 时,会先推动 check point 向前移动,空出位置再记录新的日志。启动 innodb 的时候,不管上次是正常关闭还是异常关闭,总是会进行恢复操作。因为 redo log记录的是数据页的物理变化,因此恢复的时候速度比逻辑日志(如 binlog )要快很多。


redolog的两阶段提交

什么是redolog的两阶段提交呢?就是在写redolog日志的时候,并不是直接把redolog标记为完成状态。而是中间会有一个prepare状态。为了更好理解这个概念,这里我们参考下面这个SQL语句执行流程图来分析一些这个两阶段提交的含义。


从图中,我们可以看出,一条更新语句的大致执行流程是这样的:

  1. server服务层的执行器,调用存储引擎层的API接口,去查询数据。
  2. innodb存储引擎层查询buffer pool缓存中的数据。
  3. 如果查询缓存中包含待查询的数据,则直接返回给server服务层的执行器。
  4. 如果缓存中没有结果则从磁盘中去读取数据,读取数据后,再返回给server服务层,同时把查询到的数据更新到buffer pool中的数据内容。
  5. server层的执行器收到查询后的数据后,执行更新操作。
  6. server层调用innodb存储引擎层的API接口更新数据。
  7. innodb存储引擎层更新数据到change buffer缓存池中。
  8. innddb存储引擎层记录redolog,并把其状态设置为prepare状态。
  9. innodb存储引擎层通知server的执行器,change buffer已经更新,redolog已经进入prepare待提交的就绪状态,可以记录binlog日志的。
  10. server层的执行器记录binlog到binlog的缓冲池中。(这里缓冲池中的日志何时刷盘就是通过参数sync_binlog来控制的,下面会详细介绍。)
  11. server层的执行器在记录完binlog之后,通知innodb存储引擎层,binlog已经记录完成。
  12. innodb存储引擎层收到server记录完binlog的通知后,更新redolog buffer中的redolog为commit状态。(此时redolog buffer中的日志何时刷盘就是通过参数innodb_flush_log_at_trx_commit来控制的,下面会详细介绍。)

至此,整个更新语句才真正的执行完成。通过上面的过程可以看出,在记录redolog的时候,有两个过程一个是记录redolog日志,把它的状态标记为prepare待提交状态,然后等待server层的binlog日志记录完成后,才会把redolog的日志更新为commit状态。这个过程就是redolog的两阶段提交操作。我们把上面的图简化为如下的一个图,你更能看明白:


思考问题:为什么redolog要两阶段提交?

redolog的两阶段提交是实现innodb存储引擎crash-safe功能核心。它的重要性,我们可以使用反正法来解读。试想一下:如果redolog在记录的时候不是两阶段提交。那么会有什么样的后果呢?只能有以下2种情况:

  • 先写redolog,再写binlog:在一个更新操作的提交后,如果在redolog写成功之后,还没有来得及写binlog,此时发生了异常重启。由于redolog已经记录成功,所以在异常重启之后,MySQL可以根据redolog中的内容恢复到异常重启之前数据更新后的结果,但是由于binlog没有记录这个更新操作。如果日后你拿着这个binlog还原一个新库的时候,被还原的数据库中的数据就不会有这个更新操作。此时的新库和原库数据是不一致的。
  • 先写binlog,再写redolog:在一个更新操作的提交后,如果在记录完成binlog之后,还没有来记得写redolog,此时发生了异常重启。由于binlog已经记录了修改操作,所以日后在使用这个binlog还原新库的时候,被还原的库中会包含这个更新操作,但是由于redolog没有被记录,数据库在异常重启后,并不能恢复到这个更新操作之后的数据内容。此时被还原的新库和原库中的数据也是不一致的。

综上两点可以看出,不管我们先记录哪个日志,都不能够做到数据的一致性。所以,innodb存储引擎在实现的崩溃恢复crash-safe功能的时候,就巧妙的使用了这样一个所谓的两阶段提交的方式来保证数据和日志的一致性。

你可能会有这样的疑问:在redolog两阶段提交的前提下,如果在redolog记录为prepare状态之后,binlog记录完成之后,redolog还没有来得及改为commit状态的时候,也就是上面图中②和③都执行完了,但是④没有执行,此时MySQL异常重启了,会怎么样?针对这种情况,MySQL在异常重启做恢复的时候,会去比较redolog中的内容和binlog中的内容。如果发现像上面这种情况,也会认为是成功的,因为binlog已经记录,只是差redolog中的一个commit状态没有修改成功。此时在后恢复的时候,也会从redolog中恢复出来这个数据的。这样binlog中有这个修改记录,崩溃恢复后的库中也从redolog中恢复了这个修改记录。以后用binlog还原的库和原库是一致的。从业务上看,就是虽然MySQL异常重启了,但是,我们的提交成功了。

也许你又有新的疑问:在redolog两阶段提交的前提下,如果是redolog记录并且标记为prepare状态了,后续binlog没有记录,也没有修改redolog为commit状态,也就是上面图中的②执行成功了,③和④都没有成功,此时MySQL异常重启了,会怎么样?这个时候,MySQL在重启恢复的时候,也是会比较redolog和binlog中的内容,发现只有redolog的prepare状态的日志,而binlog中没有日志。此时会把这条记录视为无效的redolog日志,也就不会恢复这样的一条修改记录。从业务上看,就是MySQL的异常重启,导致了我们的提交没有成功,我们只要重新提交一次就可以了,但是这并不会影响数据的一致性。


reldolog日志的刷盘

我们知道,在很多软件设计的时候,为了提高写数据的效率,并不是直接把数据写到磁盘上,而是先写在一个缓存池buffer中。然后从buffer中再刷新到磁盘上。relaylog在写的时候也是采用类似的方式,它有一个叫做redolog buffer的缓冲池,用于存储数据,然后再从这个缓冲池中刷新数据到磁盘中。具体什么时候把数据刷新到磁盘,是有参数innodb_flush_log_at_trx_commit来控制的,该参数的默认值为1,表示每个事务在提交之后,都会立即把这个事务的redolog持久化到磁盘上的ib_logfile文件中。

参数innodb_flush_log_at_trx_commit的取值范围[0,1,2]。而每一个值所代表的刷盘的方式是不一样的,具体如下:

  • innodb_flush_log_at_trx_commit=0:表示每秒钟都会把redolog写入到os buffer中,然后接着会调用fsync()方法,把数据持久化到磁盘上。但是考虑到在一秒内提交的事务可能不止一个,对于一个繁忙的业务系统来说,一秒钟几十个上百个事务也是很正常的。所以如果以每秒为单位去刷新redolog到磁盘,可能会丢失一秒内已提交但是未持久化到磁盘的事务的数据。
  • innodb_flush_log_at_trx_commit=1:表示每个事务提交之后就会把它的redolog写入到buffer中,然后调用fsync()方法,把事务所对应的数据持久化到磁盘上。这样可以保证在MySQL异常重启后,我们提交的事务数据不会丢失。建议这个参数的值设置为1。
  • innodb_flush_log_at_trx_commit=2:表示每个事务提交之后就会把它的redolog写入到os buffer中,但是不是马上就持久化该事务的redolog到磁盘上,而是每秒调用一次fsync()方法,从buffer向磁盘持久化数据到磁盘上。这样对于那些已经提交且写入到buffer中,但是还没有持久化到磁盘的事务的数据可能会丢失。

注意:对于上面提到的参数innodb_flush_log_at_trx_commit设置为0或者2的时候,每秒钟调用一次fsync()方法,从buffer中持久化数据到磁盘的操作,并不是100%的保证每一秒就会持久化一次。有可能大于一秒钟,也有可能一秒钟持久化多次。

当有一些DDL操作或者innodb内部的一些活动的时候,此时并不会因为innodb_flush_log_at_trx_commit的值为0或者1,就按照参数设置的方式去持久化数据到磁盘,这时候可能导致一秒钟持久化了多次数据到磁盘。Anyway,在参数innodb_flush_log_at_trx_commit不是1的情况下,是有丢失数据的风险的。

更为详细的信息,请参考MySQL的官方文档:innodb_flush_log_at_trx_commit

提到innodb_flush_log_at_trx_commit,就不得不提一句sync_binlog这个参数。其实这个参数控制着binlog日志刷新到磁盘的方式,它和innodb_flush_log_at_trx_commit参数控制着redolog刷新到磁盘的方式差不多。上面提到建议将innodb_flush_log_at_trx_commit设置为1,同理,对于sync_binlog也建议设置为1,这样就可以避免MySQL数据库在异常重启之后数据丢失的问题发生。这也是大家经常所说的双1设置的来源。


redolog对事物的支持

事物的四大特性之一:持久性 ,在MySQL的innodb存储引擎底层来说就是靠redolog来实现的。具体来说就是只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态,通过前面的介绍,我们可以知道在崩溃恢复的时候,对已经提交成功事物,就是从redolog日志文件中恢复的。所以redolog是支持事务的innodb存储引擎实现事务的持久性的方式。


redolog和binlog的区别

redolog和binlog这两个日志经常被拿出来对比,下面我们简单列出他们之间的区别和联系。


redolog其他的几个参数

redolog还有以下几个参数,这里简单介绍一下。

  • innodb_log_group_home_dir:表示我们的redolog日志文件存放在磁盘的哪个目录下。一般情况下该值为./,表示放在当前数据目录下,即datadir参数所指向的目录。
  • innodb_log_buffer_size:写redolog日志文件的时候使用的缓冲池的大小。默认为16MB。一个大的日志缓冲区允许大量的事务在提交之前不写日志到磁盘。因此,如果你有很多事务的更新,插入或删除很频繁,通过这个参数会大量的节省了磁盘I/O。
  • innodb_mirrore_log_groups: 用于指定日志镜像文件组的数量,默认为1,表示只有一个日志文件组,没有镜像。如果磁盘本身已经做好了高可用的方案,如磁盘阵列,那么可以不开启重做日志镜像的功能。

以上是关于Mysql日志---redo的主要内容,如果未能解决你的问题,请参考以下文章

MySQL两阶段提交

MySQL事务中的redo与undo

MySQL——redo日志类型中的物理日志

Mysql原理篇之redo日志--上--09

mysql日志系统之redo log和bin log

mysql物理日志redo log和逻辑日志 binlog