译自https://blogs.oracle.com/mysqlinnodb/entry/redo_logging_in_innodb
介绍
Innodb是一个能够平衡可靠性和高性能,使用非常普遍的存储引擎。支持事物,完全符合ACID原则,这些特性符合我们对关系数据库的期望,通过redo log可以让innodb引擎提供持久化的特性。
本文将提供关于redo log或者innodb引擎的日志概述:我们将关注下面的细节:
-
全局的日志系统对象,它提供重要数据构架和信息的访问入口
-
mini-transaction (mtr)用于创建所有的redo log的记录
-
全局的内存日志缓冲区,从mini transaction缓冲区写入到内存日志缓冲区,这个日志缓冲的buffer将会定时刷写到磁盘的文件中
-
磁盘上的redo log和它们的总体内部结构
-
我们将会讨论关于LSN的概念,多种LSN是如何被用于完成日志预写的
RedoLog的生成
这个话题,通过Data Organization in InnoDB的文章,我们已经明白innodb的数据文件,被认为是一个序列的具有相同大小的页.这些页通过空间ID和页ID的组合作为在ininodb系统中的唯一标识。如果我们需要读取或者编辑更多的页,它需要被加载到内存里,因此页有两个副本,内存和磁盘。
下面是redo log生成的主要步骤:
-
一个页的任何变更首先是对这个页的内存副本变更,被变更过的页,在没有被刷新到磁盘上时,是作为一个脏页标记的。
-
一个相关的redo log在内存中被生成,他是一个局部作用域的mini transaction缓冲区,将会被转换到一个全局的内存redo log的缓冲区
-
redo log的记录被从内存的redo log缓冲区写到磁盘上的redo log 文件里,这是随后刷新的,有两个步骤是被考虑的,写redo log文件,和刷新redo log文件到磁盘。这是通过操作系统负责文件缓冲落地的动作。
-
作为检查位置点操作的一部分,在短时间内,脏页被从内存刷写到磁盘
这些步骤的顺序是非常重要的,一个变更操作的redo log必须保证在对应的脏页刷到磁盘之前,先写到磁盘上。这是日志预写(WAL)的概念。
生成的redo log包含了一些重要信息,这些信息是以后数据库在还原或恢复期间所需要重新做的动作,因此redo log记录将会包含的重要信息有:页的原始设置是什么,对他们做了哪些改变。使用redo log的配置(脏页相关的)可以被用于数据库还原和恢复。
Redo log 文件
默认情况,innodb创建两个redo log文件,ib_logfile0和ib_logfile1,存放在mysql的数据目录.在msql5.6.8及以上版本,每个redo log文件的默认大小是48M,这个可以通过innodb log file size服务配置进行设置,log files的数量是通过一组innodb log file size服务配置进行控制的。
一个日志组是由一些日志文件组成,每一个都具有相同的大小,从mysql5.6开始,innodb仅支持一个日志组,因此我们不讨论这个特性
redo log文件被循环写,这意味着redo log文件被写,从第一个redo log文件开始到结束,然后继续开始下一个redo log文件,继续写,直到最后一个redo log文件,一旦最后一个redo log文件被写完,然后又重新从第一个redo log文件开始写。
日志文件被作为日志块的一个序列看待,这个日志块的大小是通过OS_FILE_LOG_BLOCK_SIZE设置的,大小是512个字节。每个日志文件有一个头部,它是通过LOG_FILE_HDR_SIZE配置的,大小定义为 4*OS_FILE_LOG_BLOCK_SIZE
Redo log 文件头
每个redo log文件包含一个头部,占用四个附带以下信息的日志块:
- 前四个字节包含日志组编号,用于该日志文件的归属
-
接下来的四个字节包含日志文件里的数据开始部分的lsn
-
第一个检查点字段位于第二个日志块的开始部分
-
第二个检查点字段位于第四个日志块的开始部分
上面提到的检查点字段包含可以确定一个检查点必要信息的:检查点编号、检查点的lsn,校验信息等等。
Log blocks(日志块)
一个redo log被作为日志块的序列看待。所有的日志块,排除属于日志文件头的部分,包括头部和尾部。头部的大小是LOG_BLOCK_HDR_SIZE个字节(被定义为12字节)。日志块trailer大小是 LOG_BLOCK_TRL_SIZE (4 字节),日志块的头部包含下面的信息:
-
日志块编号,占用四个字节
-
被记录日志字节数,占用2个字节
-
在这个日志块中,所包含的mtr日志记录组的第一个开始偏移量,如果没有则是0
-
记录日志块归属的检查点编号
日志块的trailer包含日志快正文的校验信息
日志序列编号(LSN)
日志序列编号是一个重要的概念,LSN是记录在redo log文件里的偏移量,在innodb内,日志序列编号被通过lsn_t的类型进行表示,他是一个8字节无符号整形。有不同的LSN值,那是非常有用的。下面的表格列出了一些在本文被探讨过的LSN值(说明:log_sys是全局的日志系统对象,在下一节我们会解释它)
log_sys->lsn | redo日志记录,它将会在下次使用这个LSN时被生成 |
log_sys->flushed_to_disk_lsn | redo log被刷写到 LSN. 所有redo log的记录对应的LSN是要小于 已经被安全刷新到磁盘上的LSN |
log_sys->write_lsn | 当前正在运行的写操作,它将会写到LSN上 |
log_sys->current_flush_lsn | 当前正在运行的写+刷新操作,它将会写到LSN上 |
LSN关联了脏页、redo log记录和redo log 文件,每一个redo log记录,被复制到内存缓冲区时候,它都会得到一个关联的LSN。当每个数据库的页被编辑时,会生成redo log的记录。因此,每个数据库页是被关联到一个lsn。
全局日志系统对象
全局日志系统对象log_sys包含关联innodb日志子系统的重要信息,这里只探讨关联重要信息中的redo log缓冲区和redo log文件,全局的 log_sys标识这个日志缓冲区的‘活动区域’,他包含尚未被安全的写到磁盘上的redo日志,它也标识在redo 日志文件中的区域,在那里面,日志缓冲区中的活动区域,将会被写或者刷新到磁盘。
/** Redo log buffer */struct log_t{ lsn_t lsn; /*!< log sequence number */ ulint buf_free; /*!< first free offset within the log buffer */ byte* buf_ptr; /* unaligned log buffer */ byte* buf; /*!< log buffer */ ulint buf_size; /*!< log buffer size in bytes */ ulint max_buf_free; /*!< recommended maximum value of buf_free, after which the buffer is flushed */ 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 */ lsn_t write_lsn; /*!< end lsn for the current running write */ ulint write_end_offset;/*!< the data in buffer has been written up to this offset when the current write ends: this field will then be copied to buf_next_to_write */ lsn_t current_flush_lsn;/*!< end lsn for the current running write + flush operation */ lsn_t flushed_to_disk_lsn; /*!< how far we have written the log AND flushed to disk */};
-
The member log_sys->buf 标识内存中的redo log 缓冲区. 这是一个在mtr_commit()函数内写redo log记录的缓冲区。缓冲区的大小是通过log_sys->buf_size以字节为单位设置的的。
-
The member log_sys->buf_free是在内存中的redo log缓冲区的偏移量,在那里下一个redo log的记录将会被写,这是为下一个redo log记录刷新到磁盘的,结束的偏移量
-
The member log_sys->buf_next_to_write是从redo log记录尚未被写入到redo log文件的偏移量,当redo log的缓冲区下一次被刷新到磁盘,它将是从这个位置开始,这是下一次redo log刷新到磁盘的开始偏移量.
-
The member log_sys->flushed_to_disk_lsn 标记已经被我们写到磁盘的日志文件,并且已经被刷新的,该位置的lsn,因此在该位置的lsn,磁盘上的日志文件是安全的,它将总是会小于或等于log_sys->write_lsn and log_sys->lsn
-
The member log_sys->lsn 表示当前的lsn,执行mtr_commit函数时,无论什么情况,这个对象当执行都会被更新,mtr_commit函数,会写一组从mini transaction生成的redo log记录到全局或者system wide内存中的日志缓冲区,它总是会大于或者等于 log_sys->flushed_to_disk_lsn and log_sys->write_lsn。它将是在log_sys->buf_free被写入redo log记录的lsn。
-
The member log_sys->write_lsn 表示当前写操作的redo log缓冲区lsn的结束,它会大约或等于log_sys->flushed_to_disk_lsn,小于或等于log_sys->lsn
-
The member log_sys->current_flush_lsn 表示当前运行写+刷新操作的redo log缓冲区的lsn的结束,大多数情况将等于log_sys->write_lsn
全局的log_sys对象描述内存中的redo log缓冲区和磁盘上的redo log文件的多个位置点,下面的图片展示了被全局的log_sys对象标识的位置信息,图片清晰的展示了redo log缓冲区映射到一个redo log日志文件的特地区域.
全局的内存redo log缓冲区
内存中的redo log缓冲区是全局的,凡是被用户的事物生成的所有redo log,都会被写入到这个缓冲区,它的大小配置是通过innodb_log_buffer_size设置的,默认的redo log 缓冲区大小是8M.
当一个事物,正在运行,且在修改数据,它将会生成redo LOG,落地到这个日志的buffer。日志缓冲区写和刷新到日志文件,也许是事物提交的时候,或者日志缓冲区满的时候。
当日志缓冲区满时,没有足够的空间来运行mtr_commit函数,它将会转换一组redo log日志记录到日志缓冲区,然后一个日志缓冲区的同步刷新被执行,被通过log_buffer_flush_to_disk()刷新到redo log文件。这个函数将会写日志缓冲区内容从log_sys->buf_next_to_write 到log_sys->buf_free,就LSN而言,log_buffer_flush_to_disk函数刷新redo log文件,从 log_sys->flushed_to_disk_lsn到 log_sys->lsn。
The mini transaction (mtr)
一个mtr是被用于生成所有的redo log的记录。一个mtr包含一个本地的缓冲区(称作小事物缓冲区),该缓冲区将用于存储生成的redo log记录,如果我们需要生成一组redo log的记录,要么所有的日志记录都可以生成日志文件,要么没有这样的记录,我们要把他们放到一个单一的小事物中。除了redo log记录,mtr也保持了一组已经被编辑的页的列表。
一个mtr的标准功能包含如下:
-
创建一个小事物类型的对象mtr_t
-
以 mtr_start()函数开启一个事物,它将初始化mtr的缓冲区
-
通过使用mlog_write_ulint()函数生成redo log的记录
-
使用mtr_commit() 函数提交事物,它将会把redo log记录从mtr缓冲区中转到到全局的redo log缓冲区,脏页列表,被添加到缓冲池的刷新列表。
这里给出一个mtr的定义,供参考,mtr_t::log成员包含拥有redo log记录的mtr缓冲区, mtr_t::memo成员包含被mtr污染的页的列表:
/* Mini-transaction handle and buffer */struct mtr_t{ dyn_array_t memo; /*!< memo stack for locks etc. */ dyn_array_t log; /*!< mini-transaction log */ unsigned inside_ibuf:1; /*!< TRUE if inside ibuf changes */ unsigned modifications:1; /*!< TRUE if the mini-transaction modified buffer pool pages */ unsigned made_dirty:1; /*!< TRUE if mtr has made at least one buffer pool page dirty */ ulint n_log_recs; /* count of how many page initial log records have been written to the mtr log */ ulint n_freed_pages; /* number of pages that have been freed in this mini-transaction */ ulint log_mode; /* specifies which operations should be logged; default value MTR_LOG_ALL */ lsn_t start_lsn;/* start lsn of the possible log entry for this mtr */ lsn_t end_lsn;/* end lsn of the possible log entry for this mtr */};
Redo log 记录的类型
当我们编辑一个数据库页时,一个redo log记录被生成。这个redo log记录要么包含在这个页内的变更信息(物理redo log),要么包含如何再次执行变更(逻辑redo log),innodb使用逻辑redo log记录和物理redo log记录的组合。
为了理解这些,需要考虑一个像页重组的操作,如果我们为这个操作生成一个物理的redo log记录,这个被生成的redo log记录有可能等于这个页的大小。但是用这个操作用逻辑的redo log记录代替innodb,将会大大减小redo log记录的大小。在逻辑重做日志,去记录标识一个页的重组,我们需要唯一标识这个页的信息和这个页重组的操作类型。
因此每个redlo log记录有一个类型。一个redo log的记录类型可以帮助标识这个功能(该redo log在还原期间是被应用还是被排除),redo log记录的上下文会包含该功能函数所需要的全部的参数。
Redo log 记录的生命周期
下面是redo log记录的生命周期:
-
redo log 记录被mtr生成,存储在mtr的缓冲区内。它有一些在数据库还原期间重做的操作的必要信息
-
当mtr_commit()被执行时,redo log 记录被转换到内存的redo log缓冲区。如果成功,redo log 缓冲区被刷写到redo log文件,目的是为了给新的redo log记录腾出空间
-
redo log记录有一个特殊的lsn号与它关联,这个关联是mtr_commit函数执行期间建立的,然后它在redo log文件中的位置也被确定。
-
redo log记录被从日志缓冲区转移到redo log文件,当它在写+刷新时候。这意味着现在redo log记录是永久的记录到磁盘上
- 每个redo log记录有一个被关联了脏页的列表,这个关系是通过via LSN建立的,一个redo log记录被刷新到磁盘上,在它被关联到脏页之前,一个redo log的记录可以在所有的关联脏页被刷新到磁盘以后被丢弃。
- 一个redo log记录可以被用于重新创建它的脏页的关联列表。这将会在数据库还原期间发生。
结束语
本篇幅提供一个innodb存储引擎的日志子系统的概览,日志子系统中用到的主要数据构架是mtr、内存中的redo log缓冲区和磁盘上的redo log文件。innodb的存储引擎会记录lsn value,目的是确保日志预写正确进行。
对于一个关系数据库,数据丢失是不能接受的,redo log子系统对于一个成功的数据库是非常必要的。因为redo log是与dml操作同步生成的,高效的实现是非常重要的。redo log日志的大小需要尽可能的保持越小越好。
请给我反馈