【MySQL】 日志 - redo
【MySQL】 日志 - redo
Metadata
title: 【MySQL】 日志 - redo
date: 2023-06-25 10:13
tags:
- 行动阶段/完成
- 主题场景/数据存储
- 笔记空间/KnowladgeSpace/ProgramSpace/BasicsSpace
- 细化主题/数据存储
categories:
- 数据存储
keywords:
- 数据存储
description: redo日志(Redo Log)是数据库管理系统(DBMS)中的一种重要的日志类型,用于记录数据库中发生的事务操作的变化。
概述
- redo日志(Redo Log)是数据库管理系统(DBMS)中的一种重要的日志类型,用于记录数据库中发生的事务操作的变化。
- redo日志的主要功能是保证数据库的持久性和恢复能力。
- redo 日志本质上只是记录了一下事务对数据库做了哪些修改, 在之后系统奔溃重启后可以把事务所做的任何修改都恢复出来。
- type :该条redo 日志的类型。
- space ID :表空间ID。
- page number :页号。
- data :该条redo 日志的具体内容。
- Mini-Transaction
- 对底层页面中的一次原子访问的过程称之为一个Mini-Transaction ,简称mtr。
- 以组的形式写入redo日志 — 原子
- 修改一次Max Row ID 的值算是一个Mini-Transaction
- 向某个索引对应的B+ 树中插入一条记录的过程也算是一个Mini-Transaction
redo日志的写入过程
- redo log block
- redo日志缓冲区
- redo日志写入log buffer
- buf_free 的全局变量,该变量指明后续写入的redo 日志应该写入到log buffer 中的哪个位置
- redo日志写入log buffer
- redo log block
redo日志文件
- redo日志刷盘时机
- log buffer 空间不足时
- 事务提交时
- 后台线程不停的刷刷刷
- 后台有一个线程,大约每秒都会刷新一次log buffer 中的redo 日志到磁盘。
- 正常关闭服务器时
- 做所谓的checkpoint 时(我们现在没介绍过checkpoint 的概念,稍后会仔细唠叨,稍安勿躁)
- 其他的一些情况…
- redo日志文件格式
- redo日志刷盘时机
Log Sequeue Number
- 为记录已经写入的redo 日志量,设计了一个称之为Log Sequeue Number 的全局变量,日志序列号, 简称LSN
- buf_next_to_write 标记当前log buffer 中已经有哪些日志被刷新到磁盘中了
- flushed_to_disk_lsn 刷新到磁盘中的redo 日志量的全局变量
- flush链表按照oldest_modification代表的LSN值进行排序
- flush链表中的LSN
checkpoint
- 判断某些redo日志占用的磁盘空间是否可以覆盖的依据就是它对应的脏页是否已经刷新到磁盘里。
- 做一次checkpoint其实可以分为两个步骤:
- 步骤一:计算一下当前系统中可以被覆盖的redo 日志对应的lsn 值最大是多少。
- 凡是在系统lsn值小于该节点的oldest_modification值时产生的redo日志都是可以被覆盖掉的
- 把该脏页的oldest_modification 赋值给checkpoint_lsn 。
- 步骤二:将checkpoint_lsn 和对应的redo 日志文件组偏移量以及此次checkpint 的编号写到日志文件的管理信息(就是checkpoint1 或者checkpoint2 )中。
- 步骤一:计算一下当前系统中可以被覆盖的redo 日志对应的lsn 值最大是多少。
崩溃恢复
- 确定恢复的起点
- 确定恢复的终点
redo日志
redo日志(Redo Log)是数据库管理系统(DBMS)中的一种重要的日志类型,用于记录数据库中发生的事务操作的变化。
redo日志的主要功能是保证数据库的持久性和恢复能力。当事务进行修改数据库的操作时,redo日志会记录这些操作的详细信息,包括修改的数据、修改前后的值等。通过redo日志,可以将事务的操作重新应用到数据库中,以实现事务的持久性。
redo日志的特点如下:
- 占用空间小: 存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间
- 顺序记录:redo日志以顺序的方式记录事务的操作,而不是随机记录。这样可以提高日志的写入效率。
- 非持久性:redo日志不需要永久保存在磁盘上,只需要在事务执行过程中保持在内存中和写入磁盘的缓冲区中。只有当事务提交后,才会将redo日志写入磁盘进行持久化保存。
- 循环利用:当redo日志写满时,可以将最旧的日志覆盖掉,从而实现循环利用。这样可以节省存储空间,并保证日志文件的大小不会无限增长。
redo日志格式
redo 日志本质上只是记录了一下事务对数据库做了哪些修改
各个部分的详细释义如下:
- type :该条redo 日志的类型。
- space ID :表空间ID。
- page number :页号。
- data :该条redo 日志的具体内容。
简单的redo日志类型
对页面的修改是极其简单的, redo 日志中只需要记录一下在某个页面的某个偏移量处修改了几个字节的值,具体被修改的内容是啥就好了,设计InnoDB 的大叔把这种极其简单的redo 日志称之为物理日志
根据在页面中写入数据的多少划分了几种不同的redo 日志类型:
- MLOG_1BYTE ( type 字段对应的十进制数字为1 ):表示在页面的某个偏移量处写入1个字节的redo 日志类型。
- MLOG_2BYTE ( type 字段对应的十进制数字为2 ):表示在页面的某个偏移量处写入2个字节的redo 日志类型。
- MLOG_4BYTE ( type 字段对应的十进制数字为4 ):表示在页面的某个偏移量处写入4个字节的redo 日志类型。
- MLOG_8BYTE ( type 字段对应的十进制数字为8 ):表示在页面的某个偏移量处写入8个字节的redo 日志类型。
- MLOG_WRITE_STRING ( type 字段对应的十进制数字为30 ):表示在页面的某个偏移量处写入一串数据。
MLOG_WRITE_STRING 类型的redo 日志表示写入一串数据,但是因为不能确定写入的具体数据占用多少字节,所以需要在日志结构中添加一个len 字段:
复杂一些的redo日志类型
语句对B+ 树所做更新:
表中包含多少个索引,一条INSERT 语句就可能更新多少棵B+ 树。
针对某一棵B+ 树来说,既可能更新叶子节点页面,也可能更新内节点页面,也可能创建新的页面(在该记录插入的叶子节点的剩余空间比较少,不足以存放该记录时,会进行页面的分裂,在内节点页面中添加目录项记录)。
可能更新Page Directory 中的槽信息。
Page Header 中的各种页面统计信息,比如PAGE_N_DIR_SLOTS 表示的槽数量可能会更改, PAGE_HEAP_TOP代表的还未使用的空间最小地址可能会更改, PAGE_N_HEAP 代表的本页面中的记录数量可能会更改,吧啦吧啦,各种信息都可能会被修改。
我们知道在数据页里的记录是按照索引列从小到大的顺序组成一个单向链表的,每插入一条记录,还需要更新上一条记录的记录头信息中的next_record 属性来维护这个单向链表。
还有别的吧啦吧啦的更新的地方,就不一一唠叨了…
新的redo 日志类型:
- MLOG_REC_INSERT (对应的十进制数字为9 ):表示插入一条使用非紧凑行格式的记录时的redo 日志类型。
- MLOG_COMP_REC_INSERT (对应的十进制数字为38 ):表示插入一条使用紧凑行格式的记录时的redo 日志类型。
- MLOG_COMP_PAGE_CREATE ( type 字段对应的十进制数字为58 ):表示创建一个存储紧凑行格式记录的页面的redo 日志类型。
- MLOG_COMP_REC_DELETE ( type 字段对应的十进制数字为42 ):表示删除一条使用紧凑行格式记录的redo 日志类型。
- MLOG_COMP_LIST_START_DELETE ( type 字段对应的十进制数字为44 ):表示从某条给定记录开始删除页面中的一系列使用紧凑行格式记录的redo 日志类型。
- MLOG_COMP_LIST_END_DELETE ( type 字段对应的十进制数字为43 ):与MLOG_COMP_LIST_START_DELETE类型的redo 日志呼应,表示删除一系列记录直到MLOG_COMP_LIST_END_DELETE 类型的redo 日志对应的记录为止。
Redundant是一种比较原始的行格式,它就是非紧凑的。而Compact、Dynamic以及Compressed行格式是较新的行格式,它们是紧凑的(占用更小的存储空间)。
这些类型的redo 日志既包含物理层面的意思,也包含逻辑层面的意思,具体指:
物理层面看,这些日志都指明了对哪个表空间的哪个页进行了修改。
逻辑层面看,在系统奔溃重启时,并不能直接根据这些日志里的记载,将页面内的某个偏移量处恢复成某个数据,而是需要调用一些事先准备好的函数,执行完这些函数后才可以将页面恢复成系统奔溃前的样子。
这个类型为MLOG_COMP_REC_INSERT 的redo 日志结构有几个地方需要大家注意:
- field1_len ~ fieldn_len 代表着该记录若干个字段占用存储空间的大小
- offset 代表的是该记录的前一条记录在页面中的地址。
- 通过end_seg_len 的值可以间接的计算出一条记录占用存储空间的总大小
- mismatch_index 的值也是为了节省redo 日志的大小而设立的,大家可以忽略。
redo日志格式小结
redo日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统奔溃重启后可以把事务所做的任何修改都恢复出来。
Mini-Transaction
以组的形式写入redo日志
在执行语句的过程中产生的redo 日志被设计InnoDB 的大叔人为的划分成了若干个不可分割的组,比如:
- 更新Max Row ID 属性时产生的redo 日志是不可分割的。
- 向聚簇索引对应B+ 树的页面中插入一条记录时产生的redo 日志是不可分割的。
- 向某个二级索引对应B+ 树的页面中插入一条记录时产生的redo 日志是不可分割的。
- 还有其他的一些对页面的访问操作时产生的redo 日志是不可分割的。。。
不可分割的含义
我们以向某个索引对应的B+ 树插入一条记录为例,在向B+ 树中插入这条记录之前,需要先定位到这条记录应该被插入到哪个叶子节点代表的数据页中,定位到具体的数据页之后,有两种可能的情况:
情况一:该数据页的剩余的空闲空间充足,足够容纳这一条待插入记录,那么事情很简单,直接把记录插入到这个数据页中,记录一条类型为MLOG_COMP_REC_INSERT 的redo 日志就好了,我们把这种情况称之为乐观插入。
情况二:该数据页剩余的空闲空间不足,那么事情就悲剧了,我们前边说过,遇到这种情况要进行所谓的页分裂操作,也就是新建一个叶子节点,然后把原先数据页中的一部分记录复制到这个新的数据页中,然后再把记录插入进去,把这个叶子节点插入到叶子节点链表中,最后还要在内节点中添加一条目录项记录指向这个新创建的页面。很显然,这个过程要对多个页面进行修改,也就意味着会产生多条redo 日志,我们把这种情况称之为悲观插入。
向某个索引对应的B+ 树中插入一条记录的这个过程必须是原子的
规定在执行这些需要保证原子性的操作时必须以组的形式来记录的redo 日志,在进行系统奔溃重启恢复时,针对某个组中的redo 日志,要么把全部的日志都恢复掉,要么一条也不恢复。怎么做到的呢?
- 有的需要保证原子性的操作会生成多条redo 日志,比如向某个索引对应的B+ 树中进行一次悲观插入就需要生成许多条redo 日志。
该组中的最后一条redo 日志后边加上一条特殊类型的redo 日志,该类型名称为MLOG_MULTI_REC_END , type 字段对应的十进制数字为31 ,该类型的redo 日志结构很简单,只有一个type 字段:
所以某个需要保证原子性的操作产生的一系列redo 日志必须要以一个类型为MLOG_MULTI_REC_END 结尾,就像这样:
这样在系统奔溃重启进行恢复时,只有当解析到类型为MLOG_MULTI_REC_END 的redo 日志,才认为解析到了一组完整的redo 日志,才会进行恢复。否则的话直接放弃前边解析到的redo 日志。
- 有的需要保证原子性的操作只生成一条redo 日志,比如更新Max Row ID 属性的操作就只会生成一条redo日志。
用7个比特位就足以包括所有的redo 日志类型,而type 字段其实是占用1个字节的,也就是说我们可以省出来一个比特位用来表示该需要保证原子性的操作只产生单一的一条redo 日志,
如果type 字段的第一个比特位为1 ,代表该需要保证原子性的操作只产生了单一的一条redo 日志,否则表示该需要保证原子性的操作产生了一系列的redo 日志。
Mini-Transaction的概念
对底层页面中的一次原子访问的过程称之为一个Mini-Transaction ,简称mtr。
- 修改一次Max Row ID 的值算是一个Mini-Transaction
- 向某个索引对应的B+ 树中插入一条记录的过程也算是一个Mini-Transaction
一个所谓的mtr 可以包含一组redo 日志,在进行奔溃恢复时这一组redo 日志作为一个不可分割的整体。
一个事务可以包含若干条语句,每一条语句其实是由若干个mtr 组成,每一个mtr 又可以包含若干条redo 日志,画个图表示它们的关系就是这样:
redo日志的写入过程
redo log block
为了更好的进行系统奔溃恢复,他们把通过mtr 生成的redo 日志都放在了大小为512字节的页中。
log buffer 本质上是一片连续的内存空间,被划分成了若干个512 字节大小的block 。
用来存储redo 日志的页称为block
redo log block 的示意图如下:
真正的redo 日志都是存储到占用496 字节大小的log block body 中,图中的log block header 和log block trailer 存储的是一些管理信息。
管理信息示意图:
其中log block header 的几个属性的意思分别如下:
- LOG_BLOCK_HDR_NO :每一个block都有一个大于0的唯一标号,本属性就表示该标号值。
- LOG_BLOCK_HDR_DATA_LEN :表示block中已经使用了多少字节,初始值为12 (因为log block body 从第12个字节处开始)。随着往block中写入的redo日志越来也多,本属性值也跟着增长。如果log block body已经被全部写满,那么本属性的值被设置为512 。
- LOG_BLOCK_FIRST_REC_GROUP :一条redo 日志也可以称之为一条redo 日志记录( redo log record ),
- 一个mtr 会生产多条redo 日志记录,这些redo 日志记录被称之为一个redo 日志记录组( redo logrecord group )。LOG_BLOCK_FIRST_REC_GROUP 就代表该block中第一个mtr 生成的redo 日志记录组的偏移量(其实也就是这个block里第一个mtr 生成的第一条redo 日志的偏移量)。
- LOG_BLOCK_CHECKPOINT_NO :表示所谓的checkpoint 的序号, checkpoint 是我们后续内容的重点,现在先不用清楚它的意思,稍安勿躁。
log block trailer 中属性的意思如下:
- LOG_BLOCK_CHECKSUM :表示block的校验值,用于正确性校验,我们暂时不关心它。
redo日志缓冲区
在服务器启动时就向操作系统申请了一大片称之为redo log buffer 的连续内存空间,翻译成中文就是redo日志缓冲区,我们也可以简称为log buffer 。
这片内存空间被划分成若干个连续的redo log block
默认值为16MB
redo日志写入log buffer
向log buffer 中写入redo 日志的过程是顺序的,也就是先往前边的block中写,当该block的空闲空间用完之后再往下一个block中写
当我们想往log buffer 中写入redo 日志时,第一个遇到的问题就是应该写在哪个 block 的哪个偏移量处,所以设计InnoDB 的大叔特意提供了一个称之为buf_free 的全局变量,该变量指明后续写入的redo 日志应该写入到log buffer 中的哪个位置
mtr 执行过程中可能产生若干条redo 日志,这些redo 日志是一个不可分割的组,
所以其实并不是每生成一条redo 日志,就将其插入到log buffer 中,
而是每个mtr 运行过程中产生的日志先暂时存到一个地方,
当该mtr 结束的时候,将过程中产生的一组redo 日志再全部复制到log buffer 中
假设
有两个名为T1 、T2 的事务,每个事务都包含2个mtr ,我们给这几个mtr 命名一下:
- 事务T1 的两个mtr 分别称为mtr_T1_1 和mtr_T1_2 。
- 事务T2 的两个mtr 分别称为mtr_T2_1 和mtr_T2_2 。
用示意图来描述一下这些mtr 产生的日志情况:
不同的事务可能是并发执行的,所以T1 、T2 之间的mtr 可能是交替执行的。
每当一个mtr 执行完成时,伴随该mtr 生成的一组redo 日志就需要被复制到log buffer 中,也就是说不同事务的mtr 可能是交替写入log buffer 的
redo日志文件
redo日志刷盘时机
在一些情况下它们会被刷新到磁盘里
- log buffer 空间不足时
- 事务提交时
- 后台线程不停的刷刷刷
- 后台有一个线程,大约每秒都会刷新一次log buffer 中的redo 日志到磁盘。
- 正常关闭服务器时
- 做所谓的checkpoint 时(我们现在没介绍过checkpoint 的概念,稍后会仔细唠叨,稍安勿躁)
- 其他的一些情况…
redo日志文件组
MySQL 的数据目录(使用SHOW VARIABLES LIKE ‘datadir’ 查看)下默认有两个名为ib_logfile0
和ib_logfile1
的文件, log buffer 中的日志默认情况下就是刷新到这两个磁盘文件中。
磁盘上的redo 日志文件是以一个日志文件组的形式出现的。
这些文件以ib_logfile[数字] ( 数字可以是0 、1 、2 …)的形式进行命名。
总共的redo 日志文件大小其实就是:
innodb_log_file_size × innodb_log_files_in_group 。
redo日志文件格式
将log buffer中的redo日志刷新到磁盘的本质就是把block的镜像写入日志文件中
redo 日志文件组中的每个文件大小都一样,格式也一样,都是由两部分组成:
- 前2048个字节,也就是前4个block是用来存储一些管理信息的。
- 从第2048字节往后是用来存储log buffer 中的block镜像的。
介绍一下每个redo 日志文件前2048个字节,也就是前4个特殊block的格式
这4个block分别是:
- log file header :描述该redo 日志文件的一些整体属性,看一下它的结构:
属性名 | 长度(单位:字节) | 描述 |
---|---|---|
LOG_HEADER_FORMAT | 4 | redo 日志的版本,在MySQL5.7.21 中该值永远为1 |
LOG_HEADER_PAD1 | 4 | 做字节填充用的,没什么实际意义,忽略~ |
LOG_HEADER_START_LSN | 8 | 标记本redo 日志文件开始的LSN值,也就是文件偏移量为2048字节初对应的LSN值(关于什么是LSN我们稍后再看哈,看不懂的先忽略)。 |
LOG_HEADER_CREATOR | 32 | 一个字符串,标记本redo 日志文件的创建者是谁。正常运行时该值为MySQL 的版本号,比如: “MySQL 5.7.21” ,使用mysqlbackup 命令创建的redo 日志文件的该值为”ibbackup” 和创建时间。 |
LOG_BLOCK_CHECKSUM | 4 | 本block的校验值,所有block都有,我们不关心 |
- checkpoint1 :记录关于checkpoint 的一些属性,看一下它的结构:
属性名 | 长度(单位:字节) | 描述 |
---|---|---|
LOG_CHECKPOINT_NO | 8 | 服务器做checkpoint 的编号,每做一次checkpoint ,该值就加1。 |
LOG_CHECKPOINT_LSN | 8 | 服务器做checkpoint 结束时对应的LSN 值,系统奔溃恢复时将从该值开始。 |
LOG_CHECKPOINT_OFFSET | 8 | 上个属性中的LSN 值在redo 日志文件组中的偏移量 |
LOG_CHECKPOINT_LOG_BUF_SIZE | 8 | 服务器在做checkpoint 操作时对应的log buffer 的大小 |
LOG_BLOCK_CHECKSUM | 4 | 本block的校验值,所有block都有,我们不关心 |
- 第三个block未使用,忽略~
- checkpoint2 :结构和checkpoint1 一样。
Log Sequeue Number
为记录已经写入的redo 日志量,设计了一个称之为Log Sequeue Number 的全局变量,日志序列号,简称LSN
规定初始的lsn 值为8704
在统计lsn 的增长量时,是按照实际写入的日志量加上占用的log block header 和log block trailer 来计算的
每一组由mtr生成的redo日志都有一个唯一的LSN值与其对应,LSN值越小,说明redo日志产生的越早。
flushed_to_disk_lsn
redo 日志是首先写到log buffer 中,之后才会被刷新到磁盘上的redo 日志文件。所以设计InnoDB 的大叔提出了一个称之为 buf_next_to_write 的全局变量,标记当前log buffer 中已经有哪些日志被刷新到磁盘中了。
刷新到磁盘中的redo 日志量的全局变量,称之为 flushed_to_disk_lsn 。
系统第一次启动时,该变量的值和初始的lsn 值是相同的,都是8704
如果两者的值相同时,说明log buffer中的所有redo日志都已经刷新到磁盘中了。
lsn值和redo日志文件偏移量的对应关系
lsn 的值是代表系统写入的redo 日志量的一个总和,一个mtr 中产生多少日志, lsn 的值就增加多少(当然有时候要加上log block header 和log block trailer 的大小),这样mtr 产生的日志写到磁盘中时,很容易计算某一个lsn 值在redo 日志文件组中的偏移量
flush链表中的LSN
在mtr 结束时还有一件非常重要的事情要做,就是把在mtr执行过程中可能修改过的页面加入到Buffer Pool的flush链表。
说flush链表中的脏页是按照页面的第一次修改时间从大到小进行排序的
在这个过程中会在缓存页对应的控制块中记录两个关于页面何时修改的属性:
- oldest_modification :如果某个页面被加载到Buffer Pool 后进行第一次修改,那么就将修改该页面的 mtr 开始时对应的lsn 值写入这个属性。
- newest_modification :每修改一次页面,都会将修改该页面的mtr 结束时对应的lsn 值写入这个属性。也就是说该属性表示页面最近一次修改后对应的系统lsn 值。
举例
- 假设mtr_1 执行过程中修改了页a ,那么在mtr_1 执行结束时,就会将页a 对应的控制块加入到flush链表的头部。并且将mtr_1 开始时对应的lsn ,也就是8716 写入页a 对应的控制块的oldest_modification 属性中,把mtr_1 结束时对应的lsn ,也就是8916写入页a 对应的控制块的newest_modification 属性中。画个图表示一下(为了让图片美观一些,我们把oldest_modification 缩写成了o_m ,把newest_modification 缩写成了n_m ):
- 接着假设mtr_2 执行过程中又修改了页b 和页c 两个页面,那么在mtr_2 执行结束时,就会将页b 和页c对应的控制块都加入到flush链表的头部。并且将mtr_2 开始时对应的lsn ,也就是8916写入页b 和页c对应的控制块的oldest_modification 属性中,把mtr_2 结束时对应的lsn ,也就是9948写入页b 和页c对应的控制块的newest_modification 属性中。画个图表示一下:
每次新插入到flush链表中的节点都是被放在了头部,也就是说flush链表中前边的脏页修改的时间比较晚,后边的脏页修改时间比较早。
- 接着假设mtr_3 执行过程中修改了页b 和页d ,不过页b 之前已经被修改过了,所以它对应的控制块已经被插入到了flush 链表,所以在mtr_3 执行结束时,只需要将页d 对应的控制块都加入到flush链表的头部即可。
flush链表中的脏页按照修改发生的时间顺序进行排序,也就是按照oldest_modification代表的LSN值进行排序,被多次更新的页面不会重复插入到flush链表中,但是会更新newest_modification属性的值。
checkpoint
redo日志只是为了系统奔溃后恢复脏页用的,如果对应的脏页已经刷新到了磁盘,也就是说即使现在系统奔溃,那么在重启后也用不着使用redo日志恢复该页面了,所以该redo日志也就没有存在的必要了,那么它占用的磁盘空间就可以被后续的redo日志所重用。
判断某些redo日志占用的磁盘空间是否可以覆盖的依据就是它对应的脏页是否已经刷新到磁盘里。
如果页a被刷新到了磁盘,那么它对应的控制块就会从flush链表中移除
这样mtr_1 生成的redo 日志就没有用了,它们占用的磁盘空间就可以被覆盖掉了。
提出了一个全局变量 checkpoint_lsn 来代表当前系统中可以被覆盖的redo 日志总量是多少,这个变量初始值也是8704 。
比方说现在页a被刷新到了磁盘,mtr_1生成的redo日志就可以被覆盖了,所以我们可以进行一个增加checkpoint_lsn的操作,我们把这个过程称之为做一次checkpoint。
做一次checkpoint其实可以分为两个步骤:
步骤一:计算一下当前系统中可以被覆盖的redo 日志对应的lsn 值最大是多少。
- 凡是在系统lsn值小于该节点的oldest_modification值时产生的redo日志都是可以被覆盖掉的
- 把该脏页的oldest_modification 赋值给checkpoint_lsn 。
步骤二:将checkpoint_lsn 和对应的redo 日志文件组偏移量以及此次checkpint 的编号写到日志文件的管理信息(就是checkpoint1 或者checkpoint2 )中。
维护了一个目前系统做了多少次checkpoint 的变量checkpoint_no ,每做一次checkpoint ,该变量的值就加1。
上述关于checkpoint的信息只会被写到日志文件组的第一个日志文件的管理信息中。
undefined
批量从flush链表中刷出脏页
如果后台的刷脏操作不能将脏页刷出,那么系统无法及时做checkpoint ,可能就需要用户线程同步的从flush链表中把那些最早修改的脏页( oldest_modification 最小的脏页)刷新到磁盘,这样这些脏页对应的redo 日志就没用了,然后就可以去做checkpoint 了。
查看系统中的各种LSN值
SHOW ENGINE INNODB STATUS
- Log sequence number :代表系统中的lsn 值,也就是当前系统已经写入的redo 日志量,包括写入logbuffer 中的日志。
- Log flushed up to :代表flushed_to_disk_lsn 的值,也就是当前系统已经写入磁盘的redo 日志量。
- Pages flushed up to :代表flush链表中被最早修改的那个页面对应的oldest_modification 属性值。
- Last checkpoint at :当前系统的checkpoint_lsn 值。
innodb_flush_log_at_trx_commit的用法
为了保证事务的持久性,用户线程在事务提交时需要将该事务执行过程中产生的所有redo 日志都刷新到磁盘上。
innodb_flush_log_at_trx_commit 的系统变量
- 0 :当该系统变量值为0时,表示在事务提交时不立即向磁盘中同步redo 日志,这个任务是交给后台线程做的。这样很明显会加快请求处理速度,但是如果事务提交后服务器挂了,后台线程没有及时将redo 日志刷新到磁盘,那么该事务对页面的修改会丢失。
- 1 :当该系统变量值为1时,表示在事务提交时需要将redo 日志同步到磁盘,可以保证事务的持久性。1也是innodb_flush_log_at_trx_commit 的默认值。
- 2 :当该系统变量值为2时,表示在事务提交时需要将redo 日志写到操作系统的缓冲区中,但并不需要保证将日志真正的刷新到磁盘。这种情况下如果数据库挂了,操作系统没挂的话,事务的持久性还是可以保证的,但是操作系统也挂了的话,那就不能保证持久性了。
崩溃恢复
确定恢复的起点
redo 日志文件组的第一个文件的管理信息中有两个block都存储了checkpoint_lsn 的信息,我们当然是要选取最近发生的那次checkpoint的信息。
确定恢复的终点
普通block的log block header 部分有一个称之为LOG_BLOCK_HDR_DATA_LEN 的属性,该属性值记录了当前block里使用了多少字节的空间。对于被填满的block来说,该值永远为512 。如果该属性的值不为512 ,那么就是它了,它就是此次奔溃恢复中需要扫描的最后一个block。
怎么恢复
确定了需要扫描哪些redo 日志进行奔溃恢复之后,接下来就是怎么进行恢复了。
假设现在的redo 日志文件中有5条redo 日志,如图:
由于redo 0 在checkpoint_lsn 后边,恢复时可以不管它。我们现在可以按照redo 日志的顺序依次扫描checkpoint_lsn 之后的各条redo日志,按照日志中记载的内容将对应的页面恢复出来。
- 使用哈希表
根据redo 日志的space ID 和page number 属性计算出散列值,把space ID 和page number 相同的redo日志放到哈希表的同一个槽里,如果有多个space ID 和page number 都相同的redo 日志,那么它们之间使用链表连接起来,按照生成的先后顺序链接起来的,
- 跳过已经刷新到磁盘的页面
LOG_BLOCK_HDR_NO是如何计算的
对于实际存储redo 日志的普通的log block 来说,在log block header 处有一个称之为 LOG_BLOCK_HDR_NO 的属性
使用下边的公式计算该block的LOG_BLOCK_HDR_NO 值:
((lsn / 512) & 0x3FFFFFFFUL) + 1