学习mysql8-redo日志

本文详细介绍了MySQL8的redo日志的作用、格式、同步机制和如何在系统崩溃时进行恢复。redo日志用于防止数据丢失,记录了buffer-pool与磁盘数据不一致的修改。内容涉及简单日志结构、复杂日志结构、Mini-Transaction概念,以及redo日志的刷盘时机和文件管理。在崩溃恢复时,通过检查点确定恢复起点和终点,实现数据一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

redo-log的内容和作用

数据丢失

我们在之前的buffer-pool的内容中提到过,数据的增删改查大概率的情况下,是在内存里面完成的。之后会从bufferPool不定期的刷新脏页到磁盘上,这样的一个过程会导致什么样的问题呢?
如果buffer-pool修改的内容,在发生断电或者系统崩溃的情况下,是不是就导致所有修改的内容丢失了呢。

redo-log如何力挽狂澜

那么如何才能保证所有的数据能从buffer-pool同步到磁盘上呢?
当然最简单的方式就是每次修改了数据,都及时的同步到磁盘上,按这么解决,保证了数据的一致性,牺牲的就是时间。每一次更新修改或者一个事务的提交,都需要耗费很多时间。
为此Mysql特意设计了redo日志,用来防止系统崩溃导致的数据丢失。redo日志专门用于记录buffer-pool和磁盘上不一致的一些内容,如果说不是为了恢复而使用,反而浪费了空间和时间。
redo⽇志占⽤的空间⾮常⼩,记录了存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间。redo⽇志是顺序写⼊磁盘的,如果说一个事务修改了多项内容,也是顺序IO的写入。

redo-log的格式

通过上边的内容我们知道,redo⽇志本质上只是记录了⼀下事务对数据库做了哪些修改。 设计InnoDB的⼤叔们针对事务对数据库的不同修改场景定义了多种类型的redo⽇志,但是绝⼤部分类型的redo⽇ 志都有下边这种通⽤的结构
在这里插入图片描
type表示的是redolog的类型,目前记录了有53种。space ID表空间ID,page number表示了数据页号,data是记录了实际改动的数据

简单日志结构

row_id : 多次讲到过隐藏列的这个属性用来做主键的,如果递增到256的倍数的时候,会记录在系统表空间页号为7的页⾯的相应偏移量处 写⼊8个字节的值,如果发生断电的情况下,下次row_id的值就是改处的值+256。
我们把这种只对修改了几个字节的redoLog归类成简单日志,如下:
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):表⽰在页⾯的某个偏移量处写⼊⼀串数据。
有个页面偏移量
多了个len记录长度
主要的区别就是在data这里:你也可以提出疑惑难道len为1,2,3,4的不就是上面对应的哪些类型吗,其实是对的。但是设计的人考虑到节省空间,用type就制定了对应的标准

复杂的日志结构

有时候执⾏⼀条语句会修改⾮常多的页⾯,包括系统数据页⾯和⽤户数据页⾯(⽤户数据指的就是聚簇索引和⼆级索引对应的B+树)。以⼀条INSERT语句为例,它除了要向B+树的页⾯中插⼊数据,也可 能更新系统数据Max Row ID的值。
⽐⽅说将记录插⼊到聚簇索引中时,如果定位到的叶⼦节点的剩余空间⾜够 存储该记录时,那么只更新该叶⼦节点页⾯就好,那么只记录⼀条MLOG_WRITE_STRING类型的redo⽇志,表明在页⾯的某个偏移量处增加了哪些数据就好了么?那就too young too naive了~ 别忘了⼀个数据 页中除了存储实际的记录之后,还有什么File Header、Page Header、Page Directory等等部分:
1.更新Page Directory中的槽信息
2.Page Header中的各种页⾯统计信息,⽐如PAGE_N_DIR_SLOTS表⽰的槽数量可能会更改,PAGE_HEAP_TOP代表的还未使⽤的空间最⼩地址可能会更改,PAGE_N_HEAP代表的本页⾯中的记录数量可能会更 改,吧啦吧啦,各种信息都可能会被修改
3.每插⼊⼀条记录,还需要更新上⼀条记录的记录头信息中的next_record属性来维护这个单向链表。
在这里插入图片描述

把⼀条记录插⼊到⼀个页⾯时需要更改的地⽅⾮常多:
⽅案⼀:每次修改生成一条redoLog
⽅案⼆:从哪里到哪里这一段生成redoLog
复杂的redoLog类型:
MLOG_REC_INSERT(对应的⼗进制数字为9):表⽰插⼊⼀条使⽤⾮紧凑⾏格式的记录时的redo⽇志类型。 MLOG_COMP_REC_INSERT(对应的⼗进制数字为38):表⽰插⼊⼀条使⽤紧凑⾏格式的记录时的redo⽇志类型。
⼩贴⼠: Redundant是⼀种⽐较原始的⾏格式,它就是⾮紧凑的。⽽Compact、Dynamic以及Compressed⾏格式是较新的⾏格式,它们是紧凑的(占⽤更⼩的存储空间)。
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⽇志对应 的记录为⽌。
这些类型的redoLog即包含了物理层面的意思也包含了逻辑层面的意思,怎么理解。每一条redoLog好像都指出了某个数据偏移量在具体哪个表空间的那个页内,如何对数据操作,但是又好像没有指出如何修改数据页head属性,以及前一条内容的所影响的属性,而是把redoLog作为一个参数,通过一些函数把所有需要修改的内容都处理掉。

Mini-Transaction的概念

其实也就是一个事物产生的一组redoLog是不可分割的,怎么去理解这个意思。我们插入可以分为乐观插入和悲观插入,乐观插入也就是说只产生了一条数据新增的redoLog在对应的页上,悲观插入就很头疼了,一次新增的插入导致数据页分裂,最后导致页目录可能需要重新调整,这个过程中产生了一组redoLog。设计MySQL的⼤叔把对底层页⾯中的⼀次原⼦访问的过程称之为⼀个Mini-Transaction,简称mtr,在遇到数据恢复的时候,这些redoLog内容是不能被分割的。

如何把这些redo⽇志划分到⼀个组⾥边⼉呢?
设计InnoDB的⼤叔做了⼀个很简单的⼩把戏,就是在该组中的最后⼀条redo⽇志后边加上⼀条特殊类型的redo⽇志,该类型名称 为MLOG_MULTI_REC_END,type字段对应的⼗进制数字为31,所以某个需要保证原⼦性的操作产⽣的⼀系列redo⽇志必须要以⼀个类型为MLOG_MULTI_REC_END结尾,就像这样:
在这里插入图片描述
⼀个事务可以包含若⼲条语句,每⼀条语句其实是由若⼲个mtr组成,每⼀个mtr又可以包含若⼲条redo⽇志,画个图表⽰它们的关系就是这样:在这里插入图片描述

redo-log如何同步

redo log block

其实就是用了页的概念,和之前的不一样。这次的block相当于一个存放redoLog的页
结构:
在这里插入图片描述
页头存放了下面这些内容:
LOG_BLOCK_HDR_NO前四个字节做一个唯一的标号,LOG_BLOCK_HDR_DATA_LEN表示当前页写了多少内容进去,初始值12B,最大512B。LOG_BLOCK_FIRST_REC_GROUP记录mtr产生的redoLog偏移组,也就是第一条redoLog的偏移量。另外LOG_BLOCK_CHECKPOINT_NO表示checkpoint的序号

redo⽇志缓冲区

其实日志缓冲区,有点像buffer pool。在这个场景下他就是log buffer,mysql向系统专门申请了一块内存空间用于存放redoLog
在这里插入图片描述
当我们往里面插入redoLog的时候,我们不知道从哪里开始插入,Inndb给了一个全局变量buf_free告诉你该往哪里插入了

对应磁盘上的文件

redo⽇志⽂件

redo日志刷盘时机

1.log buffer空间不⾜时
2.事务提交时
3.后台线程
4.正常关闭服务
5.做checkPoint

redo⽇志⽂件组

MySQL的数据⽬录(使⽤SHOW VARIABLES LIKE 'datadir’查看)下默认有两个名为ib_logfile0和ib_logfile1的⽂件,log buffer中的⽇志默认情况下就是刷新到这两个磁盘⽂件中。如果我们对默认 的redo⽇志⽂件不满意,可以通过下边⼏个启动参数来调节:
innodb_log_group_home_dir 该参数指定了redo⽇志⽂件所在的⽬录,默认值就是当前的数据⽬录。
innodb_log_file_size 该参数指定了每个redo⽇志⽂件的⼤⼩,在MySQL 5.7.21这个版本中的默认值为48MB, innodb_log_files_in_group 该参数指定redo⽇志⽂件的个数,默认值为2,最⼤值为100。
在这里插入图片描述
循环写入日志文件组,那么会出现后面的redolog覆盖前面的redoLog吗?

日志文件格式

我们前边说过log buffer本质上是⼀⽚连续的内存空间,被划分成了若⼲个512字节⼤⼩的block。将log buffer中的redo⽇志刷新到磁盘的本质就是把block的镜像写⼊⽇志⽂件中,所以redo⽇志⽂件其实也 是由若⼲个512字节⼤⼩的block组成。 redo⽇志⽂件组中的每个⽂件⼤⼩都⼀样,格式也⼀样,都是由两部分组成:
前2048个字节,也就是前4个block是⽤来存储⼀些管理信息的。
从第2048字节往后是⽤来存储log buffer中的block镜像的。
在这里插入图片描述log file header:描述该redo⽇志⽂件的⼀些整体属性,看⼀下它的结构:挑关键点吧,里面记录了一个LSN,标记本redo⽇志⽂件开始的LSN值,也就是⽂件偏移量为2048字节初对应的LSN值
除了log file header,还有两个checkPoint,第三个block没用

属性名 长度(单位:字节) 描述
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都有,我们不关⼼

Lsn和checkPoint

lsn

lsn:日志序列号(Log Sequeue Number),设计InnoDB的⼤叔规定初始的lsn值为8704(没有写入一条redo日志就是8704),主要用户记录多少日志刷新的log buffer。
每一个mtr对应了一组的redoLog:实际上每次往log block body里面写数据,对应的lsn如何计算呢。lsn = 8704 + redoLog实际写入的日志量+log block head+log block trailer
每⼀组由mtr⽣成的redo⽇志都有⼀个唯⼀的LSN值与其对应,LSN值越⼩,说明redo⽇志产⽣的越早。

flushed_to_disk_lsn

redo⽇志是⾸先写到log buffer中,之后才会被刷新到磁盘上的redo⽇志⽂件。所以设计InnoDB的⼤叔提出了⼀个称之为buf_next_to_write的全局变量,标记当前log buffer中已经有哪些⽇志被刷新到 磁盘中了
在这里插入图片描述
我们前边说lsn是表⽰当前系统中写⼊的redo⽇志量,这包括了写到log buffer⽽没有刷新到磁盘的⽇志,相应的,设计InnoDB的⼤叔提出了⼀个表⽰刷新到磁盘中的redo⽇志量的全局变量,称之 为flushed_to_disk_lsn。系统第⼀次启动时,该变量的值和初始的lsn值是相同的,都是8704。随着系统的运⾏,redo⽇志被不断写⼊log buffer,但是并不会⽴即刷新到磁盘,lsn的值就 和flushed_to_disk_lsn的值拉开了差距。当有新的redo⽇志写⼊到log buffer时,⾸先lsn的值会增长,但flushed_to_disk_lsn不变,随后随着不断有log buffer中的⽇志被刷新到磁盘上,flushed_to_disk_lsn的值也跟着增长。如果两者的值相同时,说明log buffer中的所有redo⽇志都已经刷新到磁盘中了

lsn值和redo⽇志⽂件偏移量的对应关系 因为lsn的值是代表系统写⼊的redo⽇志量的⼀个总和,⼀个mtr中产⽣多少⽇志,lsn的值就增加多少(当然有时候要加上log block header和log block trailer的⼤⼩),这样mtr产⽣的⽇志写到磁盘 中时,很容易计算某⼀个lsn值在redo⽇志⽂件组中的偏移量

flush链表中的LSN

我们知道⼀个mtr代表⼀次对底层页⾯的原⼦访问,在访问过程中可能会产⽣⼀组不可分割的redo⽇志,在mtr结束时,会把这⼀组redo⽇志写⼊到log buffer中。除此之外,在mtr结束时还有⼀件⾮常重要的事情要做,就是把在mtr执⾏过程中可能修改过的页⾯加⼊到Buffer Pool的flush链表

页是按照页⾯的第⼀次修改时间从⼤到⼩进⾏排序的。在这个过程中会在缓存页对应的控制块中记录两个关于页⾯何时修改的属性:
oldest_modification:如果某个页⾯被加载到Buffer Pool后进⾏第⼀次修改,那么就将修改该页⾯的mtr开始时对应的lsn值写⼊这个属性。
newest_modification:每修改⼀次页⾯,都会将修改该页⾯的mtr结束时对应的lsn值写⼊这个属性。也就是说该属性表⽰页⾯最近⼀次修改后对应的系统lsn值。
flush链表中的脏页按照修改发⽣的时间顺序进⾏排序,也就是按照oldest_modification代表的LSN值进⾏排序,被多次更新的页⾯不会重复插⼊到flush链表中,但是会更新 newest_modification属性的值。

checkpoint

redo⽇志只是为了系统奔溃后恢复脏页⽤的,如果对应的脏页已经刷新到了磁盘,也就是说即使现在系统奔溃,那么在重启后也⽤不着使⽤redo⽇志恢复该页⾯了,所以该redo⽇志也就没有存在的必要了,那么它 占⽤的磁盘空间就可以被后续的redo⽇志所重⽤。也就是说:判断某些redo⽇志占⽤的磁盘空间是否可以覆盖的依据就是它对应的脏页是否已经刷新到磁盘⾥
如何计算哪些磁盘空间可以被覆盖了?
1.先计算出当前系统最早被修改的脏页对应的oldest_modification值,那凡是在系统lsn值⼩于该节点的oldest_modification值时产⽣的redo⽇志都是可以被覆盖掉的,我们就把该脏页的oldest_modification赋值给checkpoint_lsn。
2.设计InnoDB的⼤叔维护了⼀个⽬前系统做了多少次checkpoint的变量checkpoint_no,每做⼀次checkpoint,该变量的值就加1。我们前边说过计算⼀个lsn值对应的redo⽇志⽂件组偏移量是很容易 的,所以可以计算得到该checkpoint_lsn在redo⽇志⽂件组中对应的偏移量checkpoint_offset,然后把这三个值都写到redo⽇志⽂件组的管理信息中。 我们说过,每⼀个redo⽇志⽂件都有2048个字节的管理信息,但是上述关于checkpoint的信息只会被写到⽇志⽂件组的第⼀个⽇志⽂件的管理信息中。不过我们是存储到checkpoint1中还 是checkpoint2中呢?设计InnoDB的⼤叔规定,当checkpoint_no的值是偶数时,就写到checkpoint1中,是奇数时,就写到checkpoint2中。

记录完checkpoint的信息之后,redo⽇志⽂件组中各个lsn值的关系就像这样
在这里插入图片描述

查看系统中的各种LSN值

我们可以使⽤SHOW ENGINE INNODB STATUS命令查看当前InnoDB存储引擎中的各种LSN值的情况,
⽐如: mysql> SHOW ENGINE INNODB STATUS
Log sequence number:代表系统中的lsn值,也就是当前系统已经写⼊的redo⽇志量,包括写⼊log buffer中的⽇志。
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的⽤法

不过多讲诉改项配置了
主要有0,1,2三种选项。默认是1,就是目前在讲的模式,不要求数据一致性的可以选0或者2,加快效率。

如何崩溃恢复

确定恢复的起点

从redo日志文件中提到的checkPoint结构里获取,要选取最近发⽣的那次checkpoint的信息。衡量checkpoint发⽣时间早晚的信息就是所谓 的checkpoint_no,我们只要把checkpoint1和checkpoint2这两个block中的checkpoint_no值读出来⽐⼀下⼤⼩,哪个的checkpoint_no值更⼤,说明哪个block存储的就是最近的⼀次checkpoint信息。这样 我们就能拿到最近发⽣的checkpoint对应的checkpoint_lsn值以及它在redo⽇志⽂件组中的偏移量checkpoint_offset。

确定恢复的终点

普通block的log block header部分有⼀个称之为LOG_BLOCK_HDR_DATA_LEN的属性,该属性值记录了当前block⾥使⽤了多少字节的空间。对于被填满的block来说,该值永远为512。如果该属性的值不为512,那么就是它了,它就是此次奔溃恢复中需要扫描的最后⼀个block。

怎么恢复

具体怎么恢复,当然不能一个个的恢复过去。如果redoLog日志文件很多,那我们可以考虑根据表空间Id和数据页号出手,另外应该跳过已经刷新到磁盘的页⾯。
使⽤哈希表 根据redo⽇志的space ID和page number属性计算出散列值,把space ID和page number相同的redo⽇志放到哈希表的同⼀个槽⾥,如果有多个space ID和page number都相同的redo⽇志,那么它们之 间使⽤链表连接起来,按照⽣成的先后顺序链接起来的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值