前言
同上篇一样,本文翻译自《Redo Logging in InnoDB》,原文属于InnoDB官方blog。我认为学习mysql一个比较好的方式应该是文档加源码,翻译此文的目的也是为了学习,有翻译错误的地方,烦请指出。
简介
InnoDB是一种平衡了高可靠和高性能的通用存储引擎。与其他关系型数据库一样,InnoDB支持事务和完全遵循ACID原则。其中,InnoDB的持久化是通过redolog来保证的。
本文主要关注InnoDB的redolog子系统或者log子系统。主要从以下几个方面做详细介绍:
- 全局log系统对象,主要提供对重要数据结构和信息的访问;
- 迷你事务(简称mtr),redolog记录都是以迷你事务的形式来生成的;
- 全局内存日志缓存,redolog会从mtr缓存中拷贝到全局内存日志缓存,该缓存会周期性的刷到磁盘上的redolog文件中;
- 磁盘上的redolog文件及其高级内部结构;
- LSN的基本概念以及如何使用不同的LSN值来实现预写日志(WAL);
重做日志的生成
在《InnoDB的数据组织》一文中,已经讲过,InnoDB的用户数据文件由一系列相同大小的page组成。这些page通过(space_id,page_no)这个二元组来唯一标识。每次如果需要读或者修改这些page,都要将其加载到内存中,因此,这时page就会有两份copy,一份在磁盘上,一份在内存中。这里介绍一下redolog产生过程中的高级步骤:
- 任何需要对page做的修改,都会先修改内存中的page。那些在内存中已经被修改,但是还未刷到磁盘的page被标记为脏页;
- 在局部的MTR缓存中生成相应的redolog,随后这些redolog会被拷贝到全局内存redolog缓存;
- 从redolog缓存中把redolog记录写入磁盘上的redolog文件,接着会执行flush操作。写redolog文件和刷redolog文件到磁盘,这两个步骤是相互独立的。这里需要考虑操作系统对文件的缓存;
- 脏页会在稍后的某个时刻执行checkpoint操作时刷到磁盘上;
以上几个步骤的顺序很重要,必须先刷对redolog的修改到磁盘,再刷对应的脏页到磁盘,这个就是预写日志的概念。
生成的redolog记录中保存了在数据库恢复时可以执行的相同操作的必要信息。 因此,redolog记录中包含了原page的信息以及对page的修改信息。数据库恢复的时候会重新生成redolog记录中原page的脏页。
重做日志文件
默认情况下,InnoDB会在MySQL的数据字典中创建两个redolog文件,ib_logfile0和ib_logfile1。在MySQL 5.6.8及以后的版本中,每个redolog文件的大小默认为48MB。用户可以通过修改innodb_log_file_size配置来控制redolog文件的大小,redolog文件的数量由配置项innodb_log_files_in_group来控制。 一个日志组由多个大小相同的日志文件组成,在MySQL 5.6中,InnoDB只支持一个日志组,这个会在以后再讨论。
redolog使用一种循环的方式,也就是说,在写redolog的时候,会从第一个redolog文件的开始,一直写到结尾,然后继续写下一个redolog文件,以此类推直到最后一个redolog文件的结尾,然后再从第一个redolog文件开始继续写。
redolog是由一系列固定大小为512字节的log块组成,每个redolog文件都有一个固定大小为2KB的文件头部。
重做日志文件头
redolog文件头部占4个log块,包含以下信息:
- 前4个字节表示该redolog文件所属的log组号;
- 紧接着的8个字节表示该redolog开始数据的LSN;
- 第一个checkpoint字段位于第二个log块的开始;
- 第二个checkpoint字段位于第四个log块的开始;
checkpoint字段包括了:checkpoint序列号,checkpoint的LSN,校验和等其他信息。
log块
一个redolog文件可以看成一系列的log块,除redolog文件头部的4个log块之外,其他所有log块都包含一个头部和一个尾部。头部的大小是12字节,尾部大小是4字节。log块头部包含以下信息:
- log块的编号,4个字节;
- 该log块中log所占的字节数,2字节;
- 该log块中第一个mtr log组的起始偏移,如果没有,则为0;
- 该log块所属的checkpoint编号;
log块尾部中包含了该log块内容的校验和信息。
log块的格式如下图所示:
日志序列号(LSN)
日志序列号,简称LSN,是一个比较重要的概念。LSN可以看成是redolog的一个偏移信息,在InnoDB中LSN使用一个8字节的无符号整数来表示。有些LSN值需要特别注意,下表列出来一些将要讨论的LSN值。
LSN | 描述 |
---|---|
log_sys->lsn | 下一个要生成的redolog记录的LSN值 |
log_sys->flush_to_disk_lsn | 所有LSN小于该值的redolog都已经刷到磁盘上了 |
log_sys->write_lsn | 当前正在运行的写操作将会写到该LSN |
log_sys–>current_flush_lsn | 当前正在运行的写+flush操作将会写到该LSN |
LSN是与脏页、redolog记录、redolog文件相联系的。每一个redolog记录被拷贝到内存的日志缓存中时,都会获得一个相关的LSN。当数据库页被修改的时候,会生成redolog记录,因此,每个数据库页也与一个LSN相关。page的LSN存储在page的头部结构中。页的LSN是指在页被刷到磁盘之前,redolog文件已经被刷到的LSN位置。
全局日志系统对象
全局日志系统对象log_sys包含了关于innodb日志子系统的重要信息。这里只讨论下与redolog缓存和redolog文件相关的信息。全局变量log_sys标识了日志缓存当前的活动区域,该区域中保存了还未被安全的刷到磁盘上的redologs,也标识了redolog缓存将要写或者刷到redolog文件中的区域。
log_t结构体定义(5.7.17版本storage/innodbase/include/log0log.h:617)
1 | /** Redo log buffer */ |
- log_sys->buf成员指向内存中的redolog缓存,mtr_commits()操作会将redolog记录写入到该缓存中,缓存的大小由log_sys->buf_size给出;
- log_sys->buf_free指向内存中redolog缓存的空闲位置(下一个redolog记录将要写的位置)的偏移,这也是下一个redolog刷磁盘的结束位置;
- log_sys->buf_next_to_write指向还未写到redolog文件中的那些redolog记录的偏移,下次redolog刷盘的时候,将会从该位置开始刷,这也是下一个redolog刷盘的开始位置;
- log_sys->flushed_to_disk_lsn指那些已经落盘并flush过的LSN,因此,在这个LSN之前的所有redolog已经写到磁盘上了,是安全的。这个值总是小于等于log_sys->write_lsn和log_sys->lsn;
- log_sys->lsn表示当前的LSN,每次执行mtr_commit()操作,该值都会更新。mtr_commit()函数写一个MTR中的一串redolog记录到全局或者系统的redolog缓存中,该LSN值总是大于等于log_sys->flushed_to_disk_lsn和log_sys->write_lsn,该值是一个将要从log_sys->buf_free位置开始写的redolog记录的LSN;
- log_sys->write_lsn表示当前写redolog缓存操作结束时的LSN,该值总是小于等于log_sys->lsn,大于等于log_sys->flushed_to_disk_lsn;
- log_sys->current_flush_lsn表示当前正在运行的一个write+flush操作结束时的LSN,该值几乎等于log_sys->write_lsn;
全局log_sys对象指向内存中redolog缓存和磁盘上redolog文件的各种位置,下图中展示了全局log_sys对象指向的这些位置,同时下图也清楚的表示了redolog缓存映射到redolog文件中某一个具体的位置。
全局内存redolog缓存
内存中的redolog缓存是全局的,用户产生的所有redolog事务都会写入到这个缓存中。缓存的大小可以通过配置项innodb_log_buffer_size配置项来指定,默认大小是8MB。
当一个运行中的事务要修改数据库的时候,将会产生redolog并写入到该缓存中,当事务提交或者缓存满的时候,缓存中的redolog记录将会写或刷到redolog文件中。
当redolog缓存满的时候,没有足够的空间执行mtr_commit()操作(该操作会拷贝一组redolog记录到redolog缓存中),此时会有一个同步的log_buffer_flush_to_disk()操作将redolog缓存刷到redolog文件中,这个操作的偏移区间是从log_sys->buf_next_to_write到log_sys->buf_free,这个操作的LSN范围是从log_sys->flushed_to_disk_lsn到log_sys->lsn。
迷你事务(MTR)
MTR用来生成redolog记录,将产生的redolog记录保存在一个局部缓存中。如果我们需要生成一组redolog记录(要么所有记录都刷到redolog文件,要么都没有刷到redolog文件),那么我们需要把它们在单个小的事务中执行。除了redolog记录之外,MTR还包含一个脏页的列表。
MTR的正常使用如下:
- 创建一个mtr_t类型的MTR对象;
- 使用mtr_start()函数开始一个MTR,此函数会初始化MTR缓存;
- 使用mlog_write_ulint()系列函数生成redolog记录;
- 使用mtr_commit()函数提交MTR,该函数会将redolog记录从MTR缓存拷贝到全局redolog缓存中,同时MTR中的脏页列表也会被添加到buffer pool的flush列表中;
这里给出MTR定义的一个参考,其中mtr_t::log保存redolog记录的MTR缓存, mtr_t::memo包含MTR的脏页列表。
1 | /** Mini-transaction handle and buffer */ |
重做日志记录类型
每当我们修改数据库的一个page,一个redo记录就会生成。该记录中既包含了page(物理redolog)中哪些信息被修改了,也包含了如何执行这个修改操作(逻辑redolog)。InnoDB既使用了物理redolog,也使用了逻辑redolog。
为了理解这个概念,考虑一种操作,比如page重组。如果这个操作生成了一个物理redolog,很可能这个redolog记录的大小与page的大小相等。但是,相反的是InnoDB会为这个操作生成一个逻辑redolog记录,最重要的是这么做会减小redolog记录的大小。在逻辑重做日志记录代表一个页面重组操作的情况下,我们所需要的是唯一标识一个页面的信息,以及页面重组的操作的“类型”。
因此,每一个redolog记录都有一个类型,在数据库恢复的时候,该类型能够帮助确定所使用或执行的函数,redolog记录必须包含函数所使用的所有参数。
重做日志记录的生命周期
一个redolog记录的生命周期如下:
- redolog记录首先被一个MTR产生,并记录在MTR的缓存中,它包含了数据库在恢复时的能够执行相同操作的所有必要信息;
- 当mtr_commit()执行完毕后,redolog记录被保存在内存中的全局redolog缓存中,在需要的时候redolog缓存中的redolog记录会被刷到redolog文件中,为后续的redolog记录腾出空间;
- redolog记录关联一个指定的LSN,在执行mtr_commit()时,redolog记录从MTR缓存拷贝到全局日志缓存中时,这种绑定关系被建立起来;一旦这种关系确立后,redolog记录在redolog文件中的位置也就确定下来了;
- 当执行wirte+flush操作时,redolog记录从redolog缓存刷到redolog文件中,此时redolog记录是持久化在磁盘上的;
- 每一个redolog记录都有一个相关的脏页列表,这种关系是通过LSN建立的,redolog记录必须在它相关的脏页刷盘之前落盘,只有在相关的所有脏页落盘之后,redolog记录才可以被丢弃;
- 在数据库恢复的时候,redolog记录可以用来重建相关的脏页列表;
总结
本文提供了对InnoDB存储引擎redolog子系统的一个概览。redolog子系统中使用的主要数据结构是MTR, 内存中的redolog缓存以及磁盘上的redolog文件。InnoDB存储引擎跟踪许多LSN的值来保证预写日志(WAL)的正确性。
对数据库管理系统来说,数据丢失是不可接受的,redolog子系统是保证数据不丢失的关键因素。由于redolog是在DML操作执行时同步生成的,因此必须保证redolog更有效的执行,同时redolog的大小需要尽可能的小。