MySQL日志之Redo Log(重做日志)

1. 前言

前面聊了Mysql中的Undo Log日志和InnoDB中的MVCC,今天来学习一下Redo Log日志。

事务4大特性:原子性、一致性、隔离性和持久性(ACID)。那么事务的4种特性到底是基于什么机制实现的?

  • 事务的隔离性是由锁机制实现的
  • 事务的原子性、一致性和持久性是由事务的Redo LogUndo Log来保证的。

    • Redo Log:重做日志,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性
    • Undo Log:回滚日志,回滚行记录到某个特定版本,用来保证事务的原子性、一致性

Undo Log和Redo Log都可以视为一种恢复操作

两者的区别主要在于:

  • Redo Log是存储引擎层(InnoDB)生成的日志,记录的是物理级别上的数据页修改操作,比如页号、偏移量写入了xxx数据,而不是某一行或者某几行修改成什么样,它用来恢复提交后的物理数据页。主要是为了保证数据的完整性

  • Undo Log是存储引擎层(InnoDB)生成的日志,记录的是逻辑操作日志,比如对某一行数据进行了INSERT语句操作,那么Undo Log 就记录一条与之相反的DELETE操作。主要用于事务的回滚(Undo Log 记录的是每个修改操作的逆操作)和一致性非锁定读(Undo Log回滚行记录到某种特定的版本—MVCC,即多版本并发控制)。

2. Redo Log 介绍

InnoDB存储引擎是以页为单位来管理存储空间的。在真正访问页面之前需要把在磁盘的页缓存到内存中的Buffer Pool之后才可以访问。所有的变更都必须先更新缓冲池中的数据,然后缓冲池中的脏页会以一定频率被刷入磁盘(checkPoint机制),通过缓冲池来优化CPU和磁盘之间的鸿沟,这样就可以保证整体的性能不会下降太快。

3. 为什么需要Redo Log

一方面,缓冲池可以帮助我们消除CPU和磁盘之间的鸿沟,checkpoint机制可以保证数据的最终落盘,然而由于checkoutpoint并不是每次变更的时候就触发的,而是master线程隔一段时间去处理的。所以最坏的情况就是事务提交后,刚写完缓冲池,数据库宕机了,那么这段数据就是丢失的,无法恢复。

另一方面,事务包含持久性的特性,就是说对于一个已经提交的事务,在事务提交后即使系统发生了崩溃,这个事务对数据中所做的更改页不能丢失。

那么如何保证这个持久性呢?一个简单的做法:在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘,但是这个简单粗暴的做法有些问题:

  • 修改量与刷新磁盘工作量严重不成比例

    有时候我们只修改了某个页面中的一个字节,但是我们知道在InnoDB中是以页为单位来进行磁盘IO的,也就是说我们在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘,而个页面默认是16KB大小,只修改一个字节就要刷新16KB的数据导出片上显示是太小题大作了。

  • 随机IO刷新较慢

    一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,假如该事务修改的这些页面可能并不相邻,这就意味着在将某个事务修改的Buffer Pool中的页面刷新到磁盘时需要进行很多的随机IO,随机IO比顺序IO要慢,尤其对于传统的机械硬盘来说。

另一个解决思路:我们只是想让已经提交了的事务对数据库中数据所做的修改永久生效,即使后来系统崩溃,在重启后也能把这种修改恢复出来。所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把修改了哪些东西记录一下就好

InnoDB引擎采用了WAL技术(Write-Ahead Logging)先写日志,再写磁盘,只有日志写入成功,才算事务提交成功,这里的日志就是Redo Log.当发生宕机且数据未刷到磁盘的时候,可以通过Redo Log来恢复,保证ACID中的D,这就是Redo Log的作用。

4. Redo Log记录了什么

为了应对InnoDB各种不同的需求,到Mysql8.0为止,已经有65种Redo记录,用来记录不同的信息,恢复时需要判断不同的Redo类型,来做对应的解析。根据Redo记录不同的作用对象,可将这65种REDO划分为三大类:

  • 作用于Page

  • 作用于Space

  • 提供额外信息的Logic类型

5. Redo Log的优势

5.1 好处

  • Redo Log降低了刷盘频率

  • Redo Log占用的空间非常小,存储表空间ID、页号、偏移量以及需要更新的值,所需的存储空间是很小的,刷盘快。

5.2 特点

  • Redo Log是顺序写入磁盘的;在执行事务的过程中,每执行一条语句,就可能产生若干条Redo日志,这些日志是按照产生的顺序写入磁盘的,也就是使用顺序IO,效率比随机IO快
  • 事务执行过程中,Redo Log不断记录;Redo LogBinlog的区别,Redo Log是存储引擎层产生的,而Binlog是数据库层产生的。假设一个事务,对表做10行记录插入,在这个过程中,一致不断的往Redo log中顺序记录,而Binlog只有在事务提交后,才会一次写入。

6. Redo Log的组成

Redo Log可以简单的分为以下两个部分:

  1. 重做日志的缓冲(Redo Log Buffer): 保存在内存中。在服务器启动是就像操作系统申请了一大片称之为Redo Log Buffer的连续内存空间,这片内存空间被划分称若干个连续的Redo Log Block一个Redo Log Block占用512字节大小

参数设置innodb_log_buffer_size:默认16M,最大值是4096M,最小值1M

  1. 重做日志文件(Redo Log File):保存在硬盘中(持久性)。Redo日志文件如图所示,其中ib_logfile0ib_logfile1即为Redo Log日志。

7. Redo的整体流程

以一个更新事务为例,Redo Log流转过程如下图所示:

流程说明:

  1. 先将原始数据从磁盘中读入内存,修改数据的内存拷贝
  1. 生成一条重做日志并写入Redo Log Buffer,记录的是数据被修改后的值
  1. 当事务commit时,将Redo Log Buffer中的内存刷新到Redo Log File,对Redo Log File采用追加写的方式
  1. 定期将内存中修改的数据刷新到磁盘中

8. Redo Log的刷盘策略

Redo Log的写入并不是直接写入磁盘的,InnoDB 引擎会在写Redo Log的时候先写Redo Log Buffer,之后以一定的频率刷入到真正的Redo Log File中。

问题:什么时候刷盘?

有以下集中场景可能会触发Redo Log 写文件:

  • Redo Log Buffer空间不足时

  • 事务提交

  • 后台线程

  • 做checkpoint

  • 实例shutdown时

  • binlog切换时

注意,Redo Log Buffer刷盘到Redo Log File的过程并不是真正的刷到磁盘中去,只是刷入到文件系统缓存 Page cache中(这是现在操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系统自己决定(比如page cache足够大了)。那么对于InnoDB来说就存在一个问题,如果交给系统来同步,同样如果系统宕机,那么数据也丢失了。

针对这种情况,InnoDB给出innodb_flush_log_at_trx_commit参数,该参数控制commit提交事务时,如何将Redo Log Buffer中的日志刷新到Redo Log File中,它支持三种策略:

  • 设置为0:表示每次事务提交时不进行刷盘操作(系统默认master thread每隔1s进行一次重做日志的同步),事务提交不会触发redo写操作,而是流程后台线程每秒一次的刷盘操作,因此实例crash将最多丢失1秒内的事务。
  • 设置为1(默认值):表示每次事务提交时豆浆进行同步,刷盘操作,每次事务条都要做一次fsync,这是最安全的配置,即使宕机也不会丢失事务。
  • 设置为2:每次事务提交时都只把Redo Log Buffer内容写入page cache,不进行同步,有os自己决定什么时候同步到磁盘文件,则在事务提交时只做write操作,只保证写到系统的page cache,因此crash不会丢失事务,但宕机则可能会丢失事务。

下图表示了不同配置值的持久化程度:

虽然对性能的影响时随着持久化程度的增加而增加的。通常建议在日常场景将该值设置为1,但是在系统高峰期临时修改为2以应付大负载。

1
2
3
4
5
6
mysql> show variables like 'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+

另外,InnoDB存储引擎有一个后台线程,每隔1秒就回报Redo Log Buffer中的内容写入文件系统缓存page cache,然后调用刷盘操作。

也就是说,一个没有提交事务的Redo Log记录,也可能会刷盘。因为在事务执行过程Redo Log记录时会写入到Redo Log Buffer中,这些Redo Log记录会被后台线程刷盘。

除了后台线程每秒1次的轮询操作,还有一种情况,当Redo Log Buffer占用的空间即将到innodb_log_buffer_size(默认16M)的一半的时候,后台线程会主动刷盘。