文章目录
- 6. InnoDB 磁盘文件
- 6.9 重做日志 - Redo Log
- 6.9.8 Redo Log对应磁盘上的文件是什么?
- 6.9.8.1 这么多日志文件日志写到哪个文件中?
- 6.9.8.2 什么是LSN?
- 6.9.9 Redo Log日志文件的格式?
- 6.9.9.1 Log Buffer中的Redo Log Block与磁盘中的Redo Log Block有哪些不同?
- 6.9.9.2 重做日志文件管理区包含哪些信息?
- 6.9.9.3 管理区中具体管理了什么信息?
- 6.9.10 什么是CHECKPOINT - 检查点?
- 6.9.10.1 哪些RedoLog可以被覆盖?
- 6.9.10.2 如何记录可以覆盖的日志文件位置?
- 6.9.10.3 如果没有小于 checkpoint_lsn 的日志时如何处理?
- 6.9.11 重做日志还有哪些主要的配置项?
- 6.9.12 如何查看重做日志的状态?
- 6.9.13 如何根据RedoLog进行崩溃恢复?
- 6.9.13.1 如何确定哪些日志需要恢复?
- 6.9.13.2 如何获取最新的 checkpoint_lsn 和恢复的起点?
- 6.9.13.3 如何确认恢复的终点?
- 6.9.13.4 如何进行恢复?
- 6.9.13.5 如何确定哪些日志在崩溃前已经落盘?
6. InnoDB 磁盘文件
6.9 重做日志 - Redo Log
6.9.8 Redo Log对应磁盘上的文件是什么?
- 重做日志文件位于数据目录下的
#innodb_redo目录中- 重做日志文件分为普通类型和备用类型,普通类型是正在使用的日志文件,备用是准备使用的日志文件,
InnoDB共维护32个重做日志文件,每个文件的大小等于1/32 * innodb_redo_log_capacity- 重做日志文件使用
#ib_redoN命名约定,其中N是重做日志文件编号,备用的重做日志文件使用_tmp为后缀。如下示例显示有21个活动(普通)重做日志文件和11个备用重做日志文件:
root@yudukai:/var/lib/mysql/#innodb_redo# ll total 102408 drwxr-x--- 2 mysql mysql 4096 May 29 09:08 ./ drwx------ 10 mysql mysql 4096 May 29 14:23 ../ -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo10_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo11_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo12_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo13_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo14_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo15_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo16_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo17_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo18_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo19_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo20_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo21_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo22_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo23_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo24_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo25_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo26_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo27_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo28_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo29_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo30_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo31_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo32_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo33_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo34_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo35_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo36_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo37_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo38_tmp' -rw-r----- 1 mysql mysql 3276800 May 12 10:28 '#ib_redo39_tmp' -rw-r----- 1 mysql mysql 3276800 May 29 09:08 '#ib_redo8' -rw-r----- 1 mysql mysql 3276800 May 29 09:10 '#ib_redo9' root@yudukai:/var/lib/mysql/#innodb_redo#每个普通的重做日志文件都与一个特定的
LSN取值范围相关联,用于崩溃恢时快速定位到要执行重做的日志,可以使用下面的查询显示活动重做日志文件的START_LSN和END_LSN值;
mysql> SELECT FILE_NAME, START_LSN, END_LSN FROM performance_schema.innodb_redo_log_files; +--------------------------+-----------+----------+ | FILE_NAME | START_LSN | END_LSN | +--------------------------+-----------+----------+ | ./#innodb_redo/#ib_redo8 | 26206208 | 29480960 | | ./#innodb_redo/#ib_redo9 | 29480960 | 32755712 | +--------------------------+-----------+----------+ 2 rows in set (0.00 sec) mysql>总结:
- 重做日志文件位于数据目录下的
#innodb_redo目录中,在MySQL8.0中InnoDB共维护32个重做日志文件,每个文件的大小等于1/32 * innodb_redo_log_capacity- 重做日志文件分为普通类型和备用类型,并且使用
#ib_redoN命名约定,其中N是重做日志文件编号,备用的重做日志文件使用_tmp为后缀- 重做日志的总容量可以通过系统变量
innodb_redo_log_capacity设置,8.0.30到8.0.33版本之间是128GB,8.0.34版本开始最大为512GB。
6.9.8.1 这么多日志文件日志写到哪个文件中?
通过查看
#innodb_redo目录,可以看到系统生成了32个RedoLog文件,当RedoLog从内存刷到磁盘时,先从第一个日志文件开始写,第一个写满之后顺序写到第二个,以此类推;如果最后一个也写满了,就会重新从第一个文件开始写,也就是说重做日志文件可以循环使用,如图所示:
这里可能会出现一个问题,如果循环写入的话,那么后写入的日志会不会把之前写入的内容覆盖了?当然有这个可能,为了解决这个问题,
InnoDB提出checkpoint的概念,关于checkpoint后面会详细介绍。
简单来说就是:如果之前对应的数据页都已落盘,那么就会在落盘的最后一个位置设下检查点,检查点之前的那些可以覆盖。
6.9.8.2 什么是LSN?
LSN是Log Sequence Number的简写,称为日志序号;MySQL在运行期间,只要执行DML操作就会修改数据页,意味着会不断的生成RedoLog,InnoDB为了记录生成的日志总量(字节数),设计了一个只增不减的全局变量,这个全局变量就是LSN,起始值:16*512 = 8192 ,最大值 2^64 - 1 ;- 当一个
MTR所包含的一组RedoLog被记录在Redo Log Block中时,实际是保存在Log Block Body区域,但是在统计LSN增量时,如果MTR跨Block保存时,是按照实际写入的日志大小加上Log Block Header所占的12Byte和块尾Log Block Trailer所占4Byte;- 示例:见下图
- 系统启动后初始化
Log Buffer,buf_free指向第一个block偏移量为12Byte的位置,也就是block Header之后,此时LSN也会增加12,即8192 + 12 = 8204- 假设
MTR_1中包含的一组RedoLog大小为200Byte,那么LSN就会在原来的基础上加200,即:8204 + 200 = 8404- 假设
MTR_2中包含的一组RedoLog大小为1000Byte,这里当前的Block_1已经放不下这个MTR,于是日志顺序保存在后面的Block中,占满第二个Block后,直到使用了第三个Block的一部分空间,日志保存完成;这时LSN不但要记录MTR_2中日志的总大1000Byte,还要记录Block_1+Block_2的Log Block Trailer和Block_2+Block_3的Log Block Header,总大小为:1000 + 12 * 2 + 4 * 2 = 1032,此时LSN的值为:8404 + 1032 = 9436,如下图所示:
6.9.9 Redo Log日志文件的格式?
6.9.9.1 Log Buffer中的Redo Log Block与磁盘中的Redo Log Block有哪些不同?
- 在内存中
Log Buffer是一片连续的内存空间,被划分成了若干个512字节大小的Redo Log Block用来保存Redo Log,将Log Buffer中的Redo Log刷新到磁盘,本质就是把Redo Log Block的写入日志文件中,所以Redo Log对应的日志文件其实也是由若干个512字节大小的block组成。MySQL会根据配置生成一组撤销日志文件,每个文件的格式和大小都一样,由两部分组成:
- 管理区:前
2048个字节,也就是前4个block存储一些日志文件的管理信息- 数据区:从第
2048字节往后是用来存储Log Buffer对应的Redo Log Block- 也就是说真实的日志是从每个日志文件的第
2048个字节开始写入,如图所示
- 所以
Log Buffer中的Redo Log Block与磁盘中的Redo Log Block在结构上是相同的,只不过在磁盘上多了用于文件管理的文件头信息
总结:
磁盘中
RedoLog的格式与内存中的格式相同,在内存中Log Buffer是一片连续的内存空间,被划分成了若干个512字节大小的Redo Log Block用来保存Redo Log,将Log Buffer中的Redo Log刷新到磁盘,本质就是把Redo Log Block的写入日志文件中,所以Redo Log对应的日志文件其实也是由若干个512字节大小的block组成,只不过在磁盘上多了用于文件管理的文件头信息。
6.9.9.2 重做日志文件管理区包含哪些信息?
- 关于
Redo Log Block:的结构与内存结构相同,前2048字节 分为4个Block分别为:
LOG_CHECKPOINT_1:第一个日志文件中日志头的第一个检查点信息LOG_ENCRYPTION:日志文件头信息中的加密信息LOG_CHECKPOINT_2:第一个日志文件中日志头的第二个检查点信息LOG_FILE_HDR_SIZE:日志文件头信息
6.9.9.3 管理区中具体管理了什么信息?
- 管理区各字段中的信息随着MySQL版本迭代变化非常大,这里主要介绍一些关键信息
LOG_CHECKPOINT_1、LOG_CHECKPOINT_2:主要是记录CHECKPOINT操作时对应的LSN,LSN会交替写入到LOG_CHECKPOINT_1和LOG_CHECKPOINT_2中,具体写入规则后面介绍。LOG_ENCRYPTION:LOG_FILE_HDR_SIZE中的加密信息,不做过多讨论LOG_FILE_HDR_SIZE:主要记录日志文件的一些信息,主要包括:
LOG_HEADER_FORMAT:占4字节,日志的格式标识,和MySQL版本相关,有重大更新的版本才设置相应的值,在MySQL5.7.9之前一直都是0LOG_HEADER_START_LSN:占8字节,日志文件中第一个和最后一个LSN编号LOG_HEADER_CREATOR:占32字节,记录日志的创建者,正常生成的日志一般为"MEB"+MySQL的版本号,如果是运行mysqlbackup程序,在备份过程中生成的日志,则记录MySQL的版本号
6.9.10 什么是CHECKPOINT - 检查点?
RedoLog从内存刷到磁盘上的日志文件使用循环写入的方式,也就是从第一个日志文件顺序写到最后一个日志文件,当最后一个日志文件写满时又重新写第一个日志文件,那么就可能出现日志被覆盖的情况,那么哪些日志可以被覆盖哪些不能被覆盖呢?
6.9.10.1 哪些RedoLog可以被覆盖?
首先回顾一下
RedoLog的作用,RedoLog是用作崩溃后恢复没有完成落盘的事务,也就是说当Buffer Pool中的脏页写入RedoLog,但数据页还没有落盘时发生的崩溃,当服务器重启之后可以根据RedoLog进行恢复,这也是RedoLog的应用时机,所以这种状态下的RedoLog不能被覆盖,如下图所示:
如果缓冲池中的脏页在记录
RedoLog之后,也完成了真正的落盘操作,那么相应的RedoLog就没有用了,所以这部分RedoLog就可以被覆盖,如下图所示:
经过分析可以看出,判断日志文件中的
RedoLog是否可以覆盖的依据是它对应的数据页是否已经刷新到磁盘。
6.9.10.2 如何记录可以覆盖的日志文件位置?
前面介绍过
InnoDB使用LSN是来记录RedoLog总字节数,在这个基础上InnoDB采用一个全局变量checkpoint_lsn来记录当前系统中可以被覆盖日志总量是多少,也就是说checkpoint_lsn记录已落盘脏页对应的日志结束时LSN的值,此时LSN小于checkpoint_lsn的RedoLog就可以被覆盖,如图所示:
- 当脏页刷新到磁盘之后,重新计算
checkpoint_lsn的操作,称为一次CHECKPOINT操作,也可以说是重置一次检查点,系统会用一个checkpoint_no变量记录发生CHECKPOINT操作的次数,每做一CHECKPOINT操作checkpoint_no就会加1- 由于
RedoLog文件的大小是固定的,在系统启动时已经分配好了对应的Redo Log Block,所以很容易就可以根据checkpoint_lsn计算写入位置在日志文件中的偏移量- 关于检查点相关的
checkpoint_no、checkpoint_lsn以及写入偏移量的信息会被记录在第一个日志文件的管理区,同时InnoDB规定,当checkpoint_no的值是偶数时写到checkpoint1中,是奇数时写到checkpoint2中。
总结:
CHECKPOINT也称为检查点,由于RedoLog文件是可以循环使用的,当最后一个文件写满时又会从第一个文件开始写入,这必将导致老的日志被覆盖,CHECKPOINT是标记已被刷新到磁盘的脏页刷对应的RedoLog可以被覆盖的一种操作,当日志的LSN小于已落盘脏页对应的LSN都可以被覆盖。
6.9.10.3 如果没有小于 checkpoint_lsn 的日志时如何处理?
如果日志文件中没有小于
checkpoint_lsn的日志时,表明日志文件已经使用完了,这时原来的日志不能被覆盖,InnoDB会先优先刷新脏页到磁盘,再做CHECKPOINT操作,之后再继续进行日志记录。
6.9.11 重做日志还有哪些主要的配置项?
重做日志在磁盘上所占的空间可以通过系统变量
innodb_redo_log_capacity控制,变量值以字节为单位,最大值549755813888,表示512GB,可以在选项文件或在运行时使用SET GLOBAL语句进行设置,如下所示:
# 将RedoLog的最大容量设置为8GB SET GLOBAL innodb_redo_log_capacity = 8589934592;重做日志的目录可以通过系统变量
innodb_log_group_home_dir进行设置,如果没有指定则日志文件位于数据目录的#innodb_redo目录中,如果定义了innodb_log_group_home_dir变量,则日志文件存放在该目录下的#innodb_redo目录中;
# 默认数据目录 root@yudukai:/var/lib/mysql# ll total 93888 # ... 省略 drwxr-x--- 2 mysql mysql 4096 May 29 09:08 '#innodb_redo'/ # ... 省略根据实际应用场景通过配置对应的系统变量来指定
Redo Log在磁盘上所占的空间的大小、所在目录等属性。
6.9.12 如何查看重做日志的状态?
通过状态变量
innodb_redo_log_capacity_resized显示当前重做日志容量限制:
mysql> SHOW STATUS LIKE 'Innodb_redo_log_capacity_resized'; +----------------------------------+-----------+ | Variable_name | Value | +----------------------------------+-----------+ | Innodb_redo_log_capacity_resized | 104857600 | # 100MB +----------------------------------+-----------+ 1 row in set (0.00 sec) mysql>可以通过查询
performance_schema.innodb_redo_log_files表来查看活动重做日志文件的信息
mysql> SELECT * FROM performance_schema.innodb_redo_log_files\G *************************** 1. row *************************** FILE_ID: 8 FILE_NAME: ./#innodb_redo/#ib_redo8 START_LSN: 26206208 END_LSN: 29480960 SIZE_IN_BYTES: 3276800 IS_FULL: 1 CONSUMER_LEVEL: 0 *************************** 2. row *************************** FILE_ID: 9 FILE_NAME: ./#innodb_redo/#ib_redo9 START_LSN: 29480960 END_LSN: 32755712 SIZE_IN_BYTES: 3276800 IS_FULL: 0 CONSUMER_LEVEL: 0 2 rows in set (0.00 sec) mysql>通过使用
SHOW ENGINE InnoDB STATUS访问InnoDB标准监视器输出中LOG部分查看有关Redo Log的信息
mysql> SHOW ENGINE INNODB STATUS\G *************************** 1. row *************************** Type: InnoDB Name: Status: ===================================== ... # 省略 --- LOG --- Log sequence number 21737291 # 当前的LSN Log buffer assigned up to 21737291 # Log buffer中已分配的LSN Log buffer completed up to 21737291 # Log buffer中已使用完成的LSN Log written up to 21737291 # 已写入操作缓存的LSN Log flushed up to 21737291 # 已刷新到日志文件的LSN Added dirty pages up to 21737291 # 已添加的脏页对应的LSN Pages flushed up to 21737291 # 最新添加到刷新链表页对应的LSN Last checkpoint at 21737291 # 最后一次做checkpoint的LSN Log minimum file id is 6 # 日志文件最小的编号 Log maximum file id is 6 # 普通日志文件的最大编号 24 log i/o's done, 0.00 log i/o's/second # 写入数据和速度 ... # 省略6.9.13 如何根据RedoLog进行崩溃恢复?
在
MySQL正常运行时,RedoLog不仅发挥不了它的作用而且还会对服务器的性能造成影响,但是服务器一旦崩溃,在重新启动时,就可以根据RedoLog中的记录把数据页恢复到崩溃前的状态
6.9.13.1 如何确定哪些日志需要恢复?
前面我们介绍过每一次
CHECKPOINT操作都会重新计算checkpoint_lsn,checkpoint_lsn之前的日志表示已经被刷到磁盘数据页所生成的RedoLog,既然已被刷到磁盘,也就没有必要进行恢复,所以需要恢复的是checkpoint_lsn之后的日志
6.9.13.2 如何获取最新的 checkpoint_lsn 和恢复的起点?
RedoLog文件组中的第一个文件的管理信息中有两个block checkpoint1和checkpoint2,其中都存储了checkpoint_lsn和checkpoint_no信息,每次做CHECKPOINT操作时,会在这两个block中交替写入CHECKPOINT信息,只要需要把这两个block中保存的checkpoint_no值比较一下,哪个值大就表示哪个block存储的就是最近的一次checkpoint信息。这样我们就能拿到最近发生的checkpoint对应的checkpoint_lsn值以及它在RedoLog文件组中的偏移量checkpoint_offset。
6.9.13.3 如何确认恢复的终点?
我们用之前已经掌握的内容分析一下这个问题,首先
RedoLog是顺序写入的,当一个block写满了之后再写下一个,而每一个block的log block header中都有一个名为LOG_BLOCK_HDR_DATA_LEN的属性,该属性记录了当前block使用了多少字节,对于写满的block来说,该值一定是512,所以找到第一个LOG_BLOCK_HDR_DATA_LEN的值不为512,就可以确定恢复扫描的最后一个block,这个block中的最后一条日志就是恢复的终点。
6.9.13.4 如何进行恢复?
确定了需要扫描哪些日志进行崩溃恢复之后,接下来就是怎么进行恢复了,假设现在的日志文件中有
RedoLog,如图所示:
- 第一条日志在
checkpoint_lsn之前,表示已经落盘不用恢复;checkpoint_lsn之后的日志可以通过顺序扫描的方式,根据日志记录的内容依次恢复对应的数据页InnoDB在顺序读取日志进行恢复的过程中采用了一些优化措施:首先根据日志的Space Id和Page No计算出散列值,以这个散列值为KEY,把Space Id和Page No相同的日志放到哈希表的同一个槽里,如果有多个Space Id和Page No相同的日志,那么按照日志生成的先后顺序使用链表连接起来,如下图所示:
- 组织好日志后,通过遍历哈希表,就可以一次把一个数据页中的修改全部恢复好,减少了读取数据页时的随机
I/O次数
6.9.13.5 如何确定哪些日志在崩溃前已经落盘?
checkpoint_lsn之后的日志有可能就根本没有落盘,也有可能已经落盘但没有来的及做CHECKPOINT,在恢复时如何区分呢?- 在页结构章节介绍过,磁盘上的每个页都包含一个
File Header信息,其中又包含已被刷到磁盘的LSN:FIL_PAGE_FILE_FLUSH_LSN信息,在恢复时就可以通过当前日志对应的LSN与FIL_PAGE_FILE_FLUSH_LSN进行比较,如果日志的LSN小于等于已刷新到磁盘的LSN,那就证明日志对应的数据在崩溃之前已经落盘,直接跳过即可
总结:
恢复的过程主要分为以下几步:
- 通过
checkpoint_lsn和第一个没有写满的日志页确定需要恢复日志的起始和结束位置;- 遍历日志并把
Space Id和Page No相同的日志组织在一起,以便一次性恢复完相应数据页的所有内容;- 日志的
LSN小于磁盘数据页文件记录的已刷新LSN时,表示这些数据在崩溃之前已落盘,跳过即可。
小结
RedoLog用于在数据库崩溃后恢复已提交事务还没有来的及落盘的数据,在保证事务的持久性和一致性方面起到了至关重要的作用RedoLog的写入时候,在UndoLog之后,脏页落盘之前RedoLog的结构:Type、Space ID、Page no和data- 用来组织
RedoLog的数据结构是Redo页,页的大小是512B,由Log Block Header、Log Block Body和Log Block Trailer组成,日志内容保存在Log Block Body中- 在
Log Buffer和日志文件中Redo页的格式相同并且都是顺序排列的RedoLog在磁盘上所占的空间可以通过系统变量innodb_redo_log_capacity控制,变量值以字节为单位,最大512GB