MySQL的InnoDB存储引引擎的物理文件存储体系中,除了实际的数据文件(ibd, ibdata)之外,还有两个非常重要的日志系统,分别是redo日志和undo日志。 跟Oracle类似, redo log记录了对实际数据文件的物理变更(数据文件的什么位置数据做了如何的变更)。InnoDB也是采用了WAL(日志优先落盘),也就是说在实际数据文件的修改落盘之前redo日志已经落盘,从而来保证事务的持久性。Undo日志用来保证事务的原子性和MVCC,所有的undo操作产身的数据文件的变更也会记录到redo日志中。
在原生的MySQL中,redo日志不会用来做物理主从复制,其主要的应用场景是用来进行MySQL的Crash Recovey(崩溃恢复)。关于MySQL InnoDB的崩溃恢复会在后续的文章中进行介绍。
本文主要基于MySQL 8.0介绍redo Log的基本构成。
1. redo 日志文件
从MySQL 5.6开始,已经废弃了日志组的特性(redo日志可以写多份),网上有观点认为可能是InnoDB的开发团队认为用外层的存储硬件来保证日志组的完整性可能更好一些。同时从5.7开始InnoDB的归档日志Archive也被放弃(归档日志用来归档存放所有的redo日志,redo日志系统内是采用固定尺寸的多个log文件循环写的方式来存放redo日志,如果写满了会循环到开始的位置开始写入)。
不过MySQL 8.0引入一个称之为克隆的机制,从代码的角度来看,似乎是用来实现远程克隆一个当前数据库的副本,在这个机制中又引入的新的Archive归档机制。如果读者有兴趣可以阅读一下MySQL8.0新版本源码的storage\innobase\arch和 storage\innobase\clone目录下的代码。
1.1. redo Log相关参数
innodb_log_group_home_dir
该参数用来指定redo日志存放的路径,日志文件以ib_logfile[number]来命名。
innodb_log_files_in_group
虽然MySQL已经放弃了日志组的概念,但参数名依旧保留了下来以兼容以前的配置。该参数的含义为有多少个log文件(最少为2个)。
innodb_log_file_size
表示每个文件的大小。
所以,总的redo Log的大小为 innodb_log_files_in_group * innodb_log_file_size.

1.2. redo Log循环写
redo Log以顺序的方式写入文件,当全部文件写满的时候则回到第一个文件相应的起始位置进行覆盖写(但在做redo checkpoint时,也会更新第一个日志文件的头部checkpoint标记,所以严格来讲也不算顺序写),在InnoDB内部,逻辑上Redo Log被看作一个文件,对应一个space id (InnoDB通过space的概念来组织物理存储,包括不同的表,数据字典,redo,undo等)。
MySQL InnoDB redo Log 浅析

上图是以指定innodb_log_files_in_group为3的循环写的情况。

2. Redo Log存储格式简介
尽管Redo Log有多个文件,但每个文件的组成结构是一样的,只是有一些数据只会存在的第一个Log文件(ib_logfile0)的文件头中, 例如Buffer Pool flush checkpoint信息只会写在第一个log文件的文件头中。
2.1. 日志文件存储结构概览
MySQL InnoDB redo Log 浅析
(ib_logfile0 存储概览图)

MySQL InnoDB redo Log 浅析
(ib_logfile0 之外其它redo log文件的存储概览)
InnoDB也是采用WAL的机制来保证事务的持久性,从一定的意义上来说,redo日志是顺序写,写入速度很快。数据库事务导致的数据修改进入到InnoDB存储层之后会将这些修改变更的记录存入redo log中,然后将数据的变更写入内存中的Page Pool中。InnoDB的后台线程会按照一定的规则(例如定时或者脏页的数量达到一定的比例)将脏页落盘,落盘后会记录下来当前redo Log中有多少变更日志已经实际存储到了实际的数据space文件中。redo log的总的写入量叫LSN(Log Secquence Numer)日志序列号,这个redo log变更实际写入到实际数据文件中的数量叫checkpoint LSN,表示的是有多少变更已经实际写入到了相应的数据文件中。 一旦数据库崩溃InnoDB开始恢复数据的时候,先读取checkpoint,然后从checkpoint所指示的LSN读取其之后的Redo日志进行数据恢复,从而减少Crash Recovery的时间。
比较前面两个概览图可以看到,checkpoint信息只是存储在第一个log文件头中。同时我们看到日志头中有2个checkpoint block域。InnoDB是采用2个checkpoint了轮流写的方式来保证checkpoint的安全(并不是一次写2份checkpoint, 而是轮流写)。 也由于Redo log是幂等的,应用一次和与应用两次都是一样的(在实际的应用Redo中,如果当前这一条log的lsn大于当前page的lsn,说明这一条log还没有被应用到当前page中去)。所以,即使某次checkpoint block写失败了,那么崩溃恢复的时候从上一次记录的checkpoint点开始恢复也能正确的恢复数据库事务。
2.2. Log File Header
MySQL InnoDB redo Log 浅析
(Log File Header存储结构)
Log Header Format:
这个字段以前用来标识当前Log文件属于哪个日志组,现在新的意义是用来标识当前Log为文件的格式版本。例如如果为0则表示这个redo log是有5.7.9以前的MySQL生成的。
PAD:
没有任何含义,目前仅仅用来做一些对齐处理。
Start LSN:
这个字段用在Clone和Archive场景,与一般的持久性、崩溃恢复无关,这里不做讨论。
Creator:
存储的是创建这个log文件创建者的名称的字符串。
Left Bytes:
目前没有任何含义,仅仅是用来填充占位,以便让这个block达到512字节大小。
2.3. Log Checkpoint
MySQL InnoDB redo Log 浅析
(Checkpoint block储存概览)
checkpoint number:
checkpoint number可以理解为checkpoint域写盘的次数,每次写盘递增1,同时这个值取模2可以实现2个checkpoint域轮流写。
checkpoint LSN:
该字段标示小于这个Checkpoint LSN的日志记录都已经写入到了实际的数据文件中,Crash Recovery系统从Checkpoint LSN之后的第一个MTR记录开始进行数据恢复。
checkpoint offset:
Checkpoint LSN对应在Log files中的文件偏移量,这个用来对LSN和Offset之间转换进行校准。
Buf size:
MySQL系统内只对该字段执行了写入,并为进行读取然后进行相应的处理。它标识的是系统当前Log buffer的大小。
Left bytes:
目前没有任何含义,仅仅是用来填充占位,以便让这个block达到512字节大小。但在这里最后4个字节用来存放该checkpoint域的Checksum。

2.4. Log Block的存储格式
MySQL InnoDB redo Log 浅析
(Log Block的存储格式)
Log Block Number:
Log Block的编号,从1开始递增,达到最大值(0x3FFFFFFF+1)后再继续从1开始。
Data length:
写入到当前block的字节数,包含头部12字节的大小
Firsrt Record offset
本Block内第一个mtr记录的起始偏移量
log Block Checkpoint number
该block所处在的checkpoint no
Log Records:
一个block内可以存储多条mtr记录,同样一个mtr记录可以跨越多个block.
2.5. redo日志逻辑格式到物理格式的映射
MySQL InnoDB redo Log 浅析
上图是以指定innodb_log_files_in_group为2逻辑结构到物理结构的映射

上图中上层的为Redolog的逻辑结构,可以看作是内存中的log buffer, 下层的为redo Log的实际物理文件存储,由于版面的关系,我们以innodb_log_files_in_group为2来做示例,每个log文件中仅仅包含了2个log block(Log block的多少取决与设定的innodb_log_file_size)。
每产生一个mtr记录就将其append到log buffer中去,当log buffer落盘的时候会获取固定大小的数据写入到block的数据域。当然,如果buffer中剩余的数据不足以填满一个block的数据域,也会将其写入到一个新的block中,不足的数据自动不齐,block header中的data length字段会指出有效数据的数量。

2.6. MTR简介
MTR即Mini-transaction的缩写,字面意思小事物,相对逻辑事物而言,我们把它称作物理事物。属于Innodb存储引擎的底层模块。主要用于锁和日志信息。InnoDB内部的上层模块会将事务操作转换成若干的MTR物理事务。至于上层的事务操作如何转换的MTR此操作,后续再另外单独介绍,本文只介绍一下MTR记录的格式。
每一个MTR操作会产身一条MTR Record, 下一小节我们会介绍一下MTR记录的格式。
2.7. MTR记录格式
用一句通俗的话来讲,一条MTR记录表示的是对哪个数据文件(space id)的哪一页(page)的页内某个偏移量(offset)位置做了什么改变(value).
MySQL InnoDB redo Log 浅析

(一条MTR记录的通用格式)
Type:
MTR记录的类型
Space ID:
该MTR记录修改了哪个数据文件
Page Number
该MTR记录修改了哪一页
Record Payload:
根据Type的不同,Payload内容格式也不相同,大小也不相同。后面会给出一个Type为MLOG_COMP_REC_INSERT大致的存储结构。
(MTR Type - MLOG_COMP_REC_INSERT)
MySQL InnoDB redo Log 浅析
最后列出一些MTR Record Type, 读者从名字应该就能看出这个Type的含义
/ one byte is written */
MLOG_1BYTE = 1,
/* 2 bytes ... /
MLOG_2BYTES = 2,
/
4 bytes ... */
MLOG_4BYTES = 4,
/ 8 bytes ... */
MLOG_8BYTES = 8,
/* Record insert /
MLOG_REC_INSERT = 9,
/
Mark clustered index record deleted */
MLOG_REC_CLUST_DELETE_MARK = 10,
/ Mark secondary index record deleted */
MLOG_REC_SEC_DELETE_MARK = 11,
/* update of a record, preserves record field sizes /
MLOG_REC_UPDATE_IN_PLACE = 13,
/!< Delete a record from a page /
MLOG_REC_DELETE = 14,
/
Delete record list end on index page */
MLOG_LIST_END_DELETE = 15,
/ Delete record list start on index page */
MLOG_LIST_START_DELETE = 16,
/* Copy record list end to a new created index page /
MLOG_LIST_END_COPY_CREATED = 17,
/
Reorganize an index page in ROW_FORMAT=REDUNDANT */
MLOG_PAGE_REORGANIZE = 18,
/ Create an index page */
MLOG_PAGE_CREATE = 19,
/* mark a compact index record as the predefined minimum record /
MLOG_COMP_REC_MIN_MARK = 36,
/
create a compact index page */
MLOG_COMP_PAGE_CREATE = 37,
/* compact record insert /
MLOG_COMP_REC_INSERT = 38,
/* mark compact clustered index record deleted /
MLOG_COMP_REC_CLUST_DELETE_MARK = 39,

本文由京东商城数据库技术部王治提供。