当前位置: 首页 > news >正文

双写缓冲区 Redo Log

目录

1. 双写缓冲区的作用

2. 双写缓冲区中的数据保存在哪里

3. 重做日志的作用

4. 为什么要用RedoLog,而不是直接写磁盘?

5. Redo Log 的写入时机

6. Redo Log 中记录的内容

7. Redo Log 的类型

8. DML 操作会对数据页产生什么样的影响

9. 在记录 Redo Log 时服务器崩溃了导致日入职不完整了怎么办?

10. 用来组织 Redo Log 的数据结构

11. Redo Log 在 Log Buffer 中是如何组织的

12. 同的事务在并发执行时如何记录 Redo Log?

13. Redo Log的刷盘时机

14. 不同的刷盘策略

15. Redo Log 对应的磁盘文件

16. LSN

17. Redo Log 的文件格式

18. 什么是 CHECKPOINT-检查点?

19. 如何根据 Redo Log 进行崩溃恢复


1. 双写缓冲区的作用

双写缓冲区是磁盘上的一个存储区域,当InnoDB将缓冲池中的数据页写入到磁盘上表空间数据文件之前,先将对应的页写到双写缓冲区;如果在数据真正落盘的过程中出现了意外退出,比如操作系统、存储子系统崩溃或异常断电的情况,InnoDB在崩溃恢复时可以从双写缓冲区中找到一份完好的页副本

2. 双写缓冲区中的数据保存在哪里

在MySQL 8.0.20之前,doublewrite 缓冲区位于InoDB系统表空间中。从MySQL 8.0.20开始,doublewrite缓冲区默认存储区域位于数据目录下的 doublewrite 文件中。

3. 重做日志的作用

重做日志在保证事务的持久性和一致性方面起到了至关重要的作用

重做日志用于在数据库崩溃后恢复已提交事务还没有来的及落盘的数据。重做日志以文件的形式保存在磁盘上,在正常的操作过程中,MySQL根据受影响的记录进行编码并写入重做日志文件,这些数据称为"Redo”,在重新启动时自动读取重做日志进行数据恢复。

衍生问题还没有来的及写入双写缓冲区的页,就可以通过REDO日志进行重做

4. 为什么要用RedoLog,而不是直接写磁盘?

我们来分析一下,首先明确一点,我们对数据进行的DML操作都会包含在事务当中,当完成修改并且提交事务之后,在内存中被修改的数据页就要刷新到磁盘完成持久化

那么如果这次DML操作对应的修改开始刷盘的话,当服务器崩溃,没有被刷到磁盘的数据页就从内存中丢失,这时这个事务的修改在磁盘上就是不完整的,也就是没有保证事务的一致性

为了解决这个问题,InnoDB在执行每个DML操作时,当内存中的数据页修改完成之后,把修改的内容以日志的形式保证在磁盘上,然后再对数据页进行真正的落盘操作,这样做就相当于对修改进行了一次备份,即使当服务器崩溃也不会受到影响,当服务器重启之后,可以从磁盘上的日志文件中找到上次崩溃之前没有来的及落盘的数据继续执行落盘操作

InnoDB引擎的事务采用了WAL技术(Write-Ahead Logging),基本思想是先写日志,再写磁盘,只有日志写入成功,事务才算提交成功,这里的日志就是Redo Log。当发生宕机且数据未刷到磁盘的时候,可以通过RedoLog来恢复,保证ACID中的持久性)这也是Redo Log的作用。

UNDO日志保证了事务的原子性

REDO日志保证了事务的持久性

5. Redo Log 的写入时机

当发生数据修改操作时追加重做日志,已落盘数据对应的日志位置被记录为一个检查点,检查点之前的数据被置为无效,所以重做日志文件可以循环使用。以一个更新操作为例,重做日志的写入过程与时机如下

为什么要使用Log Buffer,因为每次进行DML操作都会进行一次磁盘l/O,这样会严重影响效率,所以把日志统一写入内存中的Log Buffer,根据刷盘策略统一进行落盘操作,可以实现一次磁盘I/O写入多条日志,从而提升效率。

6. Redo Log 中记录的内容

进行DML操作时,首先要修改内存中的数据页,但是修改的数据有可能只是数据页中很少的一部分内容,甚至有可能只修改了几个字节,那么在RedoLog中是要记录整个数据页吗?当然不是,如果每次保存整个数据页的话就有太多的无用数据写入日志,严重影响效率而且浪费空间

为了节省空间提高效率,RedoLog只记录被修改的内容,比如当前的DML修改了哪个表空间、表空间中的哪个数据页,数据页中多少偏移量处的值修改成了什么

1.明确了当前日志对应的文件

2.要修改的起始位置

3.要修改的长度

4.要修改的新值

Redo Log本质上只是记录了事务对数据库做了哪些修改,修改操作包含多种场景,比如对数据行、索引页的增删改,对范围的修改与删除等等,不同场景的 redo 日志定义了不同的类型,但是绝大部分类型的 redo 日志都有下边这种通用的结构包括 : 

Type:日志类型,1BTYE

Space ID:操作所属的表空间,4BTYE

Page no:操作的数据页在表空间中的编号,4BTYE

data:日志的内容,长度不固定

data 部分又可以分为:数据页中的偏移量,修改内容的长度和具体的修改内容

7. Redo Log 的类型

日志类型总体可以分为三大类,分别是:

用于数据页的日志类型

用于表空间文件的日志类型

提供额外信息的日志类型

不同的日志类型对应的日志内容也不尽相同,而是进行DML操作时,大多数RedoLog属于用于数据页的日志类型

8. DML 操作会对数据页产生什么样的影响

一个Insert操作为例,对数据页的影响一般分为两种情况:

1. 如果写入记录所在的数据页空间充足,足够存储一条将要写入的记录,那么就可以直接写入

2. 如果写入的数据页空间不充足,无法放下这条记录,由于在数据页中真实数据是按主键顺序排列的,那么就要新建一个数据页,对原来的数据进行调整,把一部分数据复制到新的数据页中,以便在目标数据页上留出足够的空间来保存即将写入的记录

1.新创建一个数据页C

2.把数据页A中的数据行移动到数据页C

3.把数据行写入到数据页A

4.数据页C建立前后的关联关系

5.在索引页中加入一个索引目录记录

6.重新组织索引目录记录之间的关联关系以上这些操作都会生成与之对应的RedoLog

9. 在记录 Redo Log 时服务器崩溃了导致日入职不完整了怎么办?

那么这时有一个问题需要考虑,试想一下如果执行这一系统操作的时候,Redo Log 只记录了一半服务器就崩溃了,那么当服务器重启的时候如果按照 Redo Log 进行恢复,得到的结果肯定是错误的,所以在记录 Redo Log 的时候要保证一个 DML 所对应的一系列日志必须是完整的才可以执行恢复操作,否则就不执行恢复。

怎么才能标记 DML 操作对应的日志是完整的? 

Mini-Transaction就是针对以上的操作过程定义的概念,也就是说把记录一个DML操作的过程称为一个Mini-Transaction,简称MTR,一个所谓的MTR包含一个DML操作产生的一组完整日志,在进行崩溃恢复时这一组Redo Log做为一个不可分割的整体。

范围更新或删除,一个事务可以包含多个MTR

在执行DML操作的过程中,每一个对数据页的修改都会记录一条Redo Log,这些日志会被顺序记录下来,并在这组日志的最后加一条特殊的日志标识作为一个MRT的结尾,这条特殊的日志结构非常简单,只有一个 TYPE 字段类型为 MLOG_MULTI_REC_END=31,也就是日志分类中的提供额外信息的日志类型

10. 用来组织 Redo Log 的数据结构

用来组织 Redo Log 的数据结构是 Redo 页, 页的大小是 512B 也可以称为一个 Redo Log Block这个大小刚好对应磁盘上一个扇区,当日志写入磁盘时可以保证连续性

在一个 RedoLogBlock中,包含用来存储管理信息的块头Log Block Header(占12Byte)和块尾 Log Block Trailer(占4Byte),其他的空间是真正用来存储日志的区域 Log Block Body (占496B)

11. Redo Log 在 Log Buffer 中是如何组织的

在内存中 Redo Log 存储在日志缓冲区( Log Buffer )中,日志缓冲区是服务器启动时向操作系统申请的一片连续的内存区域,并被划分成若干个连续的 Redo Log Block,用来存储即将要写入磁盘日志文件的数据

向日志缓冲区中写入日志是一个顺序写入的过程,也就是从缓冲区的第一个 Redo Log Block 的Log Block Body 开始依次向后写,一个 block 的空间空间用完之后再写下一个 block

InnoDB 的提供了一个名为 buf_free 的全局变量,该变量表示后续写入日志在 Log Buffer 中的起始位置

12. 同的事务在并发执行时如何记录 Redo Log?

一个事务在执行过程中并不是每生成一条 Redo Log 就写入到 Log Buffer中,而是把生成的Redo Log 先缓存在内存的一个区域中,当一个 MTR 执行完成后把这组日志一起复制到 Log Buffer

假设有两个事务T1,T2并发执行,每个事务中都包含2个MRT,即事务T1包含mtr_t1_1和mtr_t1_2,T2包含mtr_t2_1和mtr_t2_2,如下图所示:

在并发环境下不同事务中的MTR是交替执行的,当MTR执行完成之后对应生成的Redo Log会被写入Log Buffer

13. Redo Log的刷盘时机

LogBuffer空间不足时:Log Buffer 大小是有限的,可以通过系统变量 innodb_log_buffer_size 设置, 如果当前 Log Buffer 中的 Redo Log 占用了Log Buffer 总容量一半左右会触发刷盘;

事务提交时:当事务提交时,事务中对应的 MTR 已经完全记录在了 Log Buffer 中,在数据真正落盘之前,需要把对应的 Redo Log 刷新到磁盘;

后台线程定时刷盘:后台的 Master Thread 线程,大约每秒都会把 Log Buffer 中的 Redo Log 刷新到磁盘;

正常关闭服务器时:在服务关闭之前会把会把 Log Buffer 中的 Redo Log 刷新到磁盘;

14. 不同的刷盘策略

首先我们可以通过设置系统变量 innodb_flush_log_at_trx_commit 设置写入和刷盘策略

0:日志每秒写入系统缓冲区并刷新到磁盘,未刷盘事务的日志可能会在崩溃时丢失;

1:日志在每次事务提交时写入系统缓冲区并刷新到磁盘;

2:日志在每次事务提交后写入系统缓冲区并每秒一次刷新到磁盘,未刷新日志的事务可能在崩溃时丢失。

如果启用二进制日志且设置 sync_binlog=1 时,则必须设置 innodb_flush_log_at_trx_commit = 1

值为0时:表示日志每秒写入操作系统缓存并刷新到磁盘,如果MySQL崩溃,那么在一秒内没有写入操作系统缓存的Redo Log将会丢失

值为2时:日志在每次事务提交后写入系统缓冲区并每秒一次刷新到磁盘,此时已提交的事务Redo Log全部都写入了操作系统缓存,MySQL无论是否崩溃,Redo Log都会以指定的时间刷新到磁盘,但是如果服务器崩溃或断电,将会导致操作系统缓存中的Redo Log丢失;

值为1时:日志在每次事务提交后写入系统缓冲区并每秒一次刷新到磁盘,此时已提交的事务Redo Log全部都写入了操作系统缓存,MySQL无论是否崩溃,Redo Log都会以指定的时间刷新到磁盘,但是如果服务器崩溃或断电,将会导致操作系统缓存中的Redo Log丢失;

15. Redo Log 对应的磁盘文件

重做日志文件位于数据目录下的#innodb_redo目录中

重做日志文件分为普通类型和备用类型,普普通类型是正在使用的日志文件,备用是准备使用的日志文件,InnoDB共维护32个重做日志文件,每个文件的大小等于1/32 * innodb_redo_log_capacity

重做日志文件使用#ib_redoN命名约定,其中N是重做日志文件编号,备用的重做日志文件使用_tmp为后缀。

每个普通的重做日志文件都与一个特定的[LSN取值范围相关联,用于崩溃恢时快速定位到要执行重做的日志,可以使用下面的查询显示活动重做日志文件的START_LSN和END_LSN值;

重做日志的总容量可以通过系统变量innodb_redo_log_capacity设置,最大为128GB (8.0.34 之前)之后是512G

通过查看#innodb_redo目录,可以看到系统生成了32个RedoLog文件,当RedoLog从内存刷到磁盘时,先从第一个日志文件开始写,第一个写满之后顺序写到第二个,以此类推;如果最后个也写满了,就会重新从第一个文件开始写,也就是说日重新志文件可以循环使用

16. LSN

LSN 是 Log Sequeue Number 的简写,称为日志序号

MySQL 在运行期间,只要执行 DML 操作就会修改数据页,意味着会不断的生成 Redo Log,InnoDB 为了记录生成的日志总量(字节数),设计了一个只增不减的全局变量,这个全局变量就是LSN,起始值:16*512=8192,最大值2^64-1

当一个 MTR 所包含的一组 Redo Log 被记录在 Redo Log Block 中时,实际是保存在 Log Block  Body 区域,但是在统 LSN 增量时,如果 MTR 跨 Block 保存时,是按照实际写入的日志大小加上 Log Block Header 所占的12Byte 和块尾 Log Block Trailer 所占 4Byte;

1. 系统启动后初始化LogBuffer,buf_free指向第一个block偏移量为12Byte的位置,也就是blockHeader之后,此时LSN也会增加12,即8192+12=8204

2. 假设MTR_1中包含的一组RedoLog大小为200Byte,那么LSN就会在原来的基础上加200,即:8204 + 200= 8404

3. 假设MTR_2中包含的一组RedoLog大小为1000Byte,这里当前的Block_1已经放不下这个MTR,于是日志顺序保存在后面的Block中,占满第二个Block后,直到使用了第三个Block的一部分空间,日志保存完成

4. 这时LSN不但要记录MTR_2中日志的总大1000Byte,还要记录Block_1+Block_2的LogBlockTrailer和Block_2+Block_3的Log Block Header,总大小为:1000+12*2+4*2=1032,,此时LSN的值为:8404+1032=9436

17. Redo Log 的文件格式

在内存中 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

所以Log Buffer中的Redo Log Block与磁盘中的Redo Log Block在结构上是相同的,只不过在磁盘上多了用于文件管理的文件头信息

重做日志文件管理区包含哪些信息?

LOG_CHECKPOINT_1:第一个日志文件中日志头的第一个检查点信息

LOG_ENCRYPTION日志文件头信息中的加密信息

LOG_CHECKPOINT_2:第一个日志文件中日志头的第二个检查点信息

LOG_FILE_HDR_SIZE:日志文件头信息

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之前一直都是0

        LOG_HEADER_START_LSN:占8字节,日志文件中第一个LSN编号 (和最后一个LSN)

        LOG_HEADER_CREATOR:占32字节,记录日志的创建者,正常生成的日志一般为"MEB"+MySQL的版本号,如果是运行mysql backup程序,在备份过程中生成的日志,则记录MySQL的版本号

18. 什么是 CHECKPOINT-检查点?

Redo Log 从内存刷到磁盘上的日志文件使用循环写入的方式,也就是从第一个日志文件顺序写到最后一个日志文件,当最后一个日志文件写满时又重新写第一个日志文件,那么就可能出现日志被覆盖的情况,那么哪些日志可以被覆盖哪些不能被覆盖呢?

首先回顾一下 Redo Log 的作用,Redo Log 是用作崩溃后恢复没有完成落盘的事务,也就是说当Buffer Pool 中的脏页写入 Redo Log,但数据页还没有落盘时发生的崩溃,当服务器重启之后可以根据 Redo Log 进行恢复,这也是 Redo Log 的应用时机,所以这种状态下的Redo Log不能被覆盖,如下图所示:

果缓冲池中的脏页在记录Redo Log之后,也完成了真正的落盘操作,那么相应的Redo Log就没有用了,所以这部分Redo Log就可以被覆盖,如下图所示: 

如何记录可以覆盖的日志文件位置?

前面介绍过 InnoDB 使用 LSN 是来记录 Redo Log 总字节数,在这个基础上 InnoDB 采用一个全局变量 checkpoint_lsn 来记录当前系统中可以被覆盖日志总量是多少,也就是说 checkpoint_lsn 记录已落盘脏页对应的日志结束时 LSN 的值,此时 LSN 小于 checkpoint_lsn 的 Redo Log 就可以被覆盖,如图所示:

当脏页刷新到磁盘之后,重新计算 checkpoint_lsn 的操作,称为一次 CHECKPOINT 操作,也可以说是重置一次检查点,系统会用一个 checkpoint_no 变量记录发生 CHECKPOINT 操作的次数,每做一次 CHECKPOINT 操作 checkpoint_no 就会加1

由于 Redo Log 文件的大小是固定的,在系统启动时已经分配好了对应的 Redo Log Block,所以很容易就可以根据 checkpoint_lsn 计算写入位置在日志文件中的偏移量

关于检查点相关的 checkpoint_no、checkpoint_lsn 以及写入偏移量的信息会被记录在(第一个日志文件的管理区,同时 InnoDB 规定,当 checkpoint_no 的值是偶数时写到 checkpoint1 中,是奇数时写到 checkpoint2 中。

CHECKPOINT 也称为检查点,由于Redo Log 文件是可以循环使用的,当最后一个文件写满时又会从第一个文件开始写入,这必将导致老的日志被覆盖,CHECKPOINT 是标记已被刷新到磁盘的脏页刷对应的 Redo Log 可以被覆盖的一种操作,当日志的 LSN 小于已落盘脏页对应的 LSN 都可以被覆盖。

如果没有小于checkpoint_lsn的日志时如何处理?

如果日志文件中没有小于 checkpoint_lsn 的日志时,表明日志文件已经使用完了,这时原来的日志不能被覆盖,InnoDB 会先优先刷新脏页到磁盘,再做 CHECKPOINT 操作,之后再继续进行日志记录。

19. 如何根据 Redo Log 进行崩溃恢复

何确定哪些日志需要恢复?

前面我们介绍过每一次 CHECKPOINT 操作都会重新计算 checkpoint_lsn, checkpoint_lsn之前的日志表示已经被刷到磁盘数据页所生成的Redo Log,既然已被刷到磁盘,也就没有必要进行恢复,所以需要恢复的是 checkpoint_lsn 之后的日志

何获取最新的 checkpoint_lsn 和恢复的起点?

Redo Log 文件组中的第一个文件的管理信息中有两个 block checkpoint1 和 checkpoint2 其中都存储了 checkpoint_lsn 和 checkpoint_no 信息,每次做 CHECKPOINT 操作时,会在这两个 block 中交替写入 CHECKPOINT 信息,只要需要把这两个 block 中保存的 checkpoint_lsn 值比较一下,哪个值大就表示哪个 block 存储的就是最近的一次 checkpoint 信息。这样我们就能拿到最近发生的 checkpoint 对应的 checkpoint_lsn 值以及它在 Redo Log 文件组中的偏移量checkpoint_offset。

何确认恢复的终点?

我们用之前已经掌握的内容分析一下这个问题,首先 Redo Log 是顺序写入的,当一个 block 写满了之后再写下一个,而每一个 block 的 log block header 中都有一个名为LOG_BLOCK_HDR_DATA_LEN 的属性,该属性记录了当前block使用了多少字节,对于写满的block来说,该值一定是512,所以找到第一个L0G_BL0CK_HDR_DATA_LEN的值不为512,就可以确定恢复扫描的最后一个block,这个block中的最后一条日志就是恢复的终点。

何进行恢复?

日志在checkpoint_lsn之前,表示已经落盘不用恢复

checkpoint_lsn之后的日志可以通过顺序扫描的方式,根据日志记录的内容依次恢复对应的数据页

InnoDB在顺序读取日志进行恢复的过程中采用了一些优化措施:首先根据日志的SpaceId和Page No 计算出散列值,以这个散列值为 KEY,把 Space Id 和 Page No 相同的日志放到哈希表的同一个槽里,如果有多个 Space Id和 Page No 相同的日志,那么按照日志生成的先后顺序使用链表连接起

组织好日志后,通过遍历哈希表,就可以一次把一个数据页中的修改全部恢复好,减少了读取数据页时的随机I/O次数

何确定哪些日志在崩溃前已经落盘

checkpoint_lsn之后的日志有可能就根本没有落盘,也有可能已经落盘但没有来的及做CHECKPOINT,在恢复时如何区分呢?

在页结构章节介绍过,磁盘上的每个页都包含一个FileHeader信息,其中又包含已被刷到磁盘的LSN:FIL_PAGE_FILE_FLUSH_LSN信息,在恢复时就可以通过当前日志对应的LSN与FIL_PAGE_FILE_FLUSH_LSN进行比较,如果日志的LSN小于已刷新到磁盘的LSN,那就证明日志对应的数据在崩溃之前已经落盘,直接跳过即可

恢复的过程主要分为以下几步:

1通过 checkpoint_lsn 和第一个没有写满的日志页确定需要恢复日志的起始和结束位置

2.遍历日志并把 Space Id 和 Page No 相同的日志组织在一起,以便一次性恢复完相应数据页的所有内容

3.日志的 LSN 小于磁盘数据页文件记录的已刷新 LSN 时,表示这些数据在崩溃之前已落盘,跳过即可

http://www.dtcms.com/a/295066.html

相关文章:

  • 基于GitHub的Terraform自动化管理最佳实践
  • 多服务器批量发布软件
  • Linux编程:9、线程编程-互斥锁与条件变量
  • 扫地机产品的电池CQC认证遵循哪个标准?
  • 1. 一份“从 0 到 1” 的 WSL(Windows Subsystem for Linux)速查手册
  • J2EE模式---视图助手模式
  • ospf多区域
  • git的使用,推送仓库github
  • Hierarchical-Localization 安装与常见问题解决手册
  • MSTP多生成树协议
  • 【西北工业大学公开课】导引系统原理(全61讲)周军 -个人笔记版 5000字
  • 基于多种机器学习的水质污染及安全预测分析系统的设计与实现【随机森林、XGBoost、LightGBM、SMOTE、贝叶斯优化】
  • Parasoft为金融服务打造统一测试平台,提升安全、合规与交付效率
  • Shell函数
  • 无人设备遥控器之无线网络技术篇
  • Linux 一文详谈Vim编辑器的使用
  • 面试150 最大子数组和
  • C语言学习(days09)
  • useEffect
  • Java异常处理核心原理与最佳实践
  • 数据驱动未来:构建强大AI系统的基石
  • QPixmap::scaled参数说明
  • 床上肢体康复机器人的机械结构设计cad【7张】三维图+设计说明书
  • 1、黑马点评复盘(短信登录-Session或Redis实现)
  • pytest简单使用和生成测试报告
  • FCW(Front Collision Warning)前碰撞预警功能介绍
  • 借助DataStream和多路复用实现可观察性
  • mybatis条件语句的查询与注解的使用以及mybatis与servelet结合查询
  • 数据结构系列之AVL树
  • 主要科技公司与新创公司 AI Agent 进展调研