0%

『译』InnoDB的重做日志

前言

同上篇一样,本文翻译自《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产生过程中的高级步骤:

  1. 任何需要对page做的修改,都会先修改内存中的page。那些在内存中已经被修改,但是还未刷到磁盘的page被标记为脏页;
  2. 在局部的MTR缓存中生成相应的redolog,随后这些redolog会被拷贝到全局内存redolog缓存;
  3. 从redolog缓存中把redolog记录写入磁盘上的redolog文件,接着会执行flush操作。写redolog文件和刷redolog文件到磁盘,这两个步骤是相互独立的。这里需要考虑操作系统对文件的缓存;
  4. 脏页会在稍后的某个时刻执行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块的格式如下图所示:

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/** Redo log buffer */
struct log_t{
....
lsn_t lsn; /*!< log sequence number */
ulint buf_free; /*!< first free offset within the log buffer in use */
....
byte* buf_ptr; /*!< unaligned log buffer, which should be of double of buf_size */
byte* buf; /*!< log buffer currently in use;
this could point to either the first
half of the aligned(buf_ptr) or the
second half in turns, so that log
write/flush to disk don't block
concurrent mtrs which will write
log to this buffer */
bool first_in_use; /*!< true if buf points to the first
half of the aligned(buf_ptr), false
if the second half */
ulint buf_size; /*!< log buffer size of each in bytes */
ulint max_buf_free; /*!< recommended maximum value of
buf_free for the buffer in use, after
which the buffer is flushed */
....
/** The fields involved in the log buffer flush @{ */
ulint buf_next_to_write;/*!< first offset in the log buffer
where the byte content may not exist
written to file, e.g., the start
offset of a log record catenated
later; this is advanced when a flush
operation is completed to all the log
groups */
volatile bool is_extending; /*!< this is set to true during extend the log buffer size */
lsn_t write_lsn; /*!< last written lsn */
lsn_t current_flush_lsn;/*!< end lsn for the current runningwrite + flush operation */
lsn_t flushed_to_disk_lsn; /*!< how far we have written the log AND flushed to disk */
....
};
  • 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缓存

内存中的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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/** Mini-transaction handle and buffer */
struct mtr_t {
/** State variables of the mtr */
struct Impl {
/** memo stack for locks etc. */
mtr_buf_t m_memo;

/** mini-transaction log */
mtr_buf_t m_log;

/** true if mtr has made at least one buffer pool page dirty */
bool m_made_dirty;

/** true if inside ibuf changes */
bool m_inside_ibuf;

/** true if the mini-transaction modified buffer pool pages */
bool m_modifications;

/** Count of how many page initial log records have been
written to the mtr log */
ib_uint32_t m_n_log_recs;

/** specifies which operations should be logged; default
value MTR_LOG_ALL */
mtr_log_t m_log_mode;
#ifdef UNIV_DEBUG
/** Persistent user tablespace associated with the
mini-transaction, or 0 (TRX_SYS_SPACE) if none yet */
ulint m_user_space_id;
#endif /* UNIV_DEBUG */
/** User tablespace that is being modified by the
mini-transaction */
fil_space_t* m_user_space;
/** Undo tablespace that is being modified by the
mini-transaction */
fil_space_t* m_undo_space;
/** System tablespace if it is being modified by the
mini-transaction */
fil_space_t* m_sys_space;

/** State of the transaction */
mtr_state_t m_state;

/** Flush Observer */
FlushObserver* m_flush_observer;

#ifdef UNIV_DEBUG
/** For checking corruption. */
ulint m_magic_n;
#endif /* UNIV_DEBUG */

/** Owning mini-transaction */
mtr_t* m_mtr;
};
....
}

重做日志记录类型

每当我们修改数据库的一个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的大小需要尽可能的小。

如果对您有帮助