redo日志介绍 #
redo日志是为了保证事务的持久性而存在的。
持久性是指事务提交后,即使系统发生了崩溃,这个事务对数据库的修改也不会丢失。
为什么不直接刷脏页到磁盘上呢?
- 刷新完整数据页太浪费。可能只修改了几个字节,却要刷16KB(默认页大小)的数据到磁盘上。
- 随机I/O较慢。一个事务可能修改多个爷们,这些页面可能并不相连。
redo日志的好处:
- redo 日志占用空间小。只存储表空间ID、页号、偏移量、需要更新的值,需要的存储空间较小。
- redo 日志顺序写。redo日志是按照产生的顺序写的,不需要随机I/O,速度较快。
redo日志格式 #
redo日志的通用结构如下:

- type:日志类型。
- space ID:表空间ID。
- page number:页号。
- data:日志的具体内容。
日志类型 #
- 简单日志:纯粹的物理日志,只记录一下再某个页面的某个偏移量处修改了几个字节的值及修改后的内容。
- 复杂日志:有时执行一条语句时可能会修改非常多的页面和内容。兼具有物理日志和逻辑日志的特点。
redo日志文件组 #
事务执行过程中会产生很多redo日志,其中一些日志被InnoDB认为是不可分割的,被划分成一个redo日志组。 例如:
- 向聚簇索引对应B+树的页面插入一条记录时产生的redo日志是一组,是不可分割的。
在向数据页中插入内容时,可能会触发页分裂,这个时候就会产生多个redo日志。这些日志是不可分割的。
怎么把多个日志划分成一个redo日志组? 在改组的最后一条redo日志后面加上一条特殊类型的redo日志。
redo日志缓冲器 #
InnoDB咋已写入redo日志时不直接写入到磁盘,而是在内存中先写入到一个叫做redo日志缓冲区的地方(简称为log buffer)。
redo log是顺序写入的。
redo日志刷盘时机 #
redo日志先被写入log buffer,在一定条件下才会被刷盘到磁盘上:
- log buffer空间不足。
- 事务提交时。
- 刷脏页。
- 后台线程每秒钟会刷盘一次。
- 关闭数据库时。
- checkpoint时。
lsn #
lsn是日志序列号(Log Sequence Number)的缩写,用来记录当前总共写入的redo日志量。
checkpoint_lsn是检查点lsn,表示当前系统中可以被覆盖的redo日志总量是多少。(应该说可以被覆盖的redo日志的lsn的上限)。
redo日志占用的磁盘空间在它对应的脏页被刷新到磁盘后即可被覆盖。
执行checkpoint的意思就是增加checkpoint_lsn的值。 执行一次checkpoint可以分成两个步骤:
- 计算
- 将
崩溃恢复 #
在服务器正常运行的情况下,redo日志没用。
确定恢复的起点 #
我们需要从对应的lsn值为checkpoint_lsn的redo日志开始恢复。
lsn值小于checkpoint_lsn的redo日志可以被覆盖。这些redo日志的脏页都已经被刷新到磁盘上了,没有必要恢复。 lsn值不小于checkpoint_lsn的redo日志不能被覆盖。因为它们对应的脏页可能被刷盘了,也可能没有被刷盘。
确定恢复的终点 #
redo日志是顺序写入的,写满一个block之后再往下一个block中写。 普通block的header中有一个属性值来记录当前block使用了多少字节的空间。 对于被填满的block来说,该值永远为512。如果该属性的值不为512,那么它就是此次崩溃恢复中需要扫描的最后一个block。
怎么恢复 #
按照redo日志的顺序,依次扫描checkpoint_lsn之后的各条redo日志,按照日志中记载的内容将对应的页面恢复过来。
InnoDB使用了一些措施来加速恢复过程:
- 使用哈希表。 根据redo日志的表空间号和页号属性来计算哈希值,表空间号和页号相同的redo日志会被放到哈希表的同一个槽中。如果有多个表空间号和页号相同的redo日志,它们之间使用链表链接起来(按照生成顺序)。 然后遍历哈希表。对同一个页面进行修改的redo日志会被放到同一个槽中,这样可以一次性将一个页面修好(避免随机I/O),加快恢复速度。
- 跳过已经刷新到磁盘中的页面。 在恢复时怎样知道一个redo日志对应的脏页是否在崩溃时已经被刷新到磁盘上了呢? 每个页面都有一个File Header,其中有一个FILE_PAGE_LSN属性,记录了页面最后一次被修改的lsn值。如果在执行了某次checkpoint后,有脏页被刷新到了磁盘中,那么该页的FILE_PAGE_LSN属性值肯定大于checkpoint_lsn,这种情况下,该页就不需要恢复了。