redo log(1)—mysql进阶(五十九)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: redo log(1)—mysql进阶(五十九)

上篇文章说了我们可以用begin 和statr transaction,提交可以commit,rollback回滚,可以指定回滚到保存点,也可以设置全局变量set autocommit off。也会隐式提交,比如开启事务后,如果操作或者新增了表,比如create table等语句,会隐式提交前面的sql。

transaction (2)—mysql进阶(五十八)

本篇文章会频繁用到我们前面说过的innoDb记录行格式,页面格式,索引原理,表空间组成等各基础知识,如果下面的文章理解不了,建议先去阅读之前的文章。


Redo日志是什么


我们知道innoDB是以页为单位来存储空间的,我们在增删查该数据本质就是访问表空间的页面,读页面,写页面,创建页面等。前面说的buffer pool时候,在查询时,需要把磁盘上的页面数据缓存到内存的buffer pool才能访问。但是说道事务持久性,对于已提交的事务,但是在事务提交后发生了系统崩溃,这个数据的更改对于数据库也不能丢失。


但如果我们只在内存的buffer pool中修改了页面,假设事务提交后出现故障,导致内存里的数据都失效了,那么这个已提交的事务对数据库中所更改的也跟着丢失,但我们不能忍受的。那么如何保证持久性呢,一个很简单的做法就是在事务提交完成之前,吧所有修改的页面刷新到磁盘上,但这样简单粗暴的做法有点问题:


刷新完整数据太浪费:有时候我们在页仅仅修改了一个字节,我们又知道innoDB是以页为单位来进行磁盘I/O,也就是我们提交时候不得不把一个完整的页面刷新到磁盘,我们又知道一个页是16kb,只要修改一个字节就刷新16kb到磁盘太浪费了。

随机I/O刷起来比较慢:一个事务里可能有多个sql,一个sql里面可能改变多个不同的页,但是这些页面不会是相邻的,这就意味着要把这些不相邻的页刷新到磁盘上是随机I/O,相对于传统机械硬盘来说,随机I/O比顺序I/O慢很多。

怎么办呢,回到我们的初心,我们是为了在提交事务的时候,即是发生系统宕机,也能在重启的时候,吧修改的数据恢复,所以我们实现这个目的,没必要每次吧修改的数据刷新到磁盘,可以用一个文件吧这个修改日志记录下来,于是redo 日志就出来了,比如

将第0号表空间的100号页面偏移量为1000处的值更新为2。

这样在系统崩溃的情况下,也可以再重启后按redo日志里面的内容重新持久化。与之前事务提交时吧修改内存里的数据刷新到磁盘相比,吧redo日志刷到磁盘的好处:

redo日志占用的空间非常小:存储表空间id,页号,偏移量以及需要更新的值,占用空间非常小。

redo日志是顺序写入磁盘的:在执行事务中,每执行一个sql,可能会有上千条redo 日志,这些都是顺序I/O写入磁盘的。


Redo日志格式


我们前面知道了redo日志记录的是记录到磁盘I/O的数据,innoDB有多种不同类型的redo日志,但绝大部分redo日志都有这种通用格式:

Type:该条redo日志类型。(mysql5.7.21版本后,innoDB有53种不同类型)

SpaceId:表空间id。

Page number:页号。

Data :该条redo日志j具体内容。

简单的redo日志类型

我们前面说过,innoDB的记录行格式说过,如果表没有主见或者唯一键,则innoDB会添加一个row_id的隐藏列作为主键,为这个隐藏列赋值的方式如下:


服务器会在内存维护一个全局变量,每当给含row_id表插入一条记录的时候,全局的row_id都会增加1。

每当这个变量值为256的倍数时,就会把该变量的值刷新到系统表空间页号7的页面中一个称为max row id的属性处。

当系统启动时,会将max row id属性加载到内存,将该值加上256之后赋值给我们前边提到的全局变量(因为在上次关机时该全局变量的值可能大于max row id属性值)。

这种max row id属性占用的存储空间是8个字节,当某个事物向某个包含row_id的表插入一条记录,并且为该记录分配的row_id是256的倍数时,就会向系统表空间页号7的页面相应偏移量处写入8个字节的值。但是我们知道,这个写入实际是buffer pool中完成的,我们需要为这条修改的数据记录一条redo日志,以便在系统不小心宕机时可以根据redo日志恢复出来。这种修改很简单,只需要在redo日志记录在某个页面的某个偏移量处修改了几个字节的值,具体被修改的内容是啥就好了,mysql吧这种简单的redo日志称为物理日志,并且根据页面写入数据的多少划分多少种不同的redo日志类型:

MLOG_1BYTE(type字段对应的十进制数字是1):表示在页面某个偏移量处写入1个字节的redo日志类型。

MOLG_2BYTE(type字段对应的十进制数字是2):表示在某个页面偏移量处写入2个字节的redo日志类型。

MOLG_4BYTE(type字段对应的十进制数字是4):表示在某个页面偏移量处写入4个字节的redo日志类型

MOLG_8BYTE(type字段对应的十进制数字是8):表示在某个页面偏移量处写入8个字节的redo日志类型。

MOLG_WRITE_STRING(type字段对应的十进制数字是30):表示在某个页面偏移量处写入一串字符串。

我们前面提高的max_row_id属性实际占用8个字节,所以在修改页面该属性时,会记录一条MOLG_8BYTE的redo日志,MOLG_8BYTE的redo日志结构如下:type,spaceid,page number,offset,具体数据。其中offset记录的就是偏移量,其他的结构与molg_8byte类似。但是molg_write_string与前面不同,因为不确定具体占用多少字节,所以需要在日志中加入len字段,表示具体占用的字节数。(只要将len字段填上1,2,4,8这些数字,不就可以代替哪些molg_8byte吗,还不是为了省空间,能少一个len字段就少一个)


复杂一些的redo日志类型


有时候执行一条语句会修改多个页面,比如系统数据页面和用户数据页面(用户数据指的就是聚簇索引和二级索引对应的b+树)。以一条insert语句为例,除了要给b+树插入数据,也可能更新系统数据max_row_id的值,不过对于我们用户来说,更关心b+树的更新:

表中包含多少索引,一条insert语句会更新多少棵b+树。

针对某一棵树,即可能更新叶子节点,也可能更新内节点,也可能创建新的页面(在该记录插入叶子节点的剩余空间比较少,不足以存放该记录,就会进行页分裂,在内节点添加目录项记录)。

在语法执行过程中,insert语句所有修改的页面都需要保存到redo日志去。这句话说得轻巧,但需要怎么做才能保存进去呢。比方说如果定位到叶子节点剩余空间足够,那么只要记录一条mlog_write_string类型的redo的日志就好了吗?当然不是,别忘了数据存储的页还有file header,page header,page directory等等,所以每往叶子节点插入一条数据,还有其他地方需要更新:

可能更新page Directory的槽信息。

Page header 各种页面统计信息,比如page_n_dir_slots表示槽数量可能会更改,page_heap_top代表未使用的空间,page_n_heap代表本页中的记录数量可能会更改等各种信息。

我们都知道数据页的记录是按索引组成的一个单向链表,每插入一条数据, 每插入一条数据,还需要更新上一条记录的记录头信息中next_recored属性来维护单向列表。

如果我们使用上面介绍的简单物理redo日志来记录这些修改,有两种解决方案:

方案一:在每个修改的地方都记录一条redo日志。

也就是只要有地方修改就记录一条,这种显而易见,修改的地方和需要记录的地方太多。

方案二:将整个页面第一个修改的地方和最后一个修改的地方之间的所有数据当做是一条redo日志的中的具体数据。这样缺点也很明显,不可能这之间的所有数据都会更改,哪些没有更改的数据也全部记录到redo日志里,不是非常浪费内存吗。

正因为这些方案比较浪费,所以innoDB本着勤俭节约的初心,设计出了更完善的redo日志存储方案:

MLOG_REC_INSERT(对应的十进制数字为9):表示插入一条使用非紧凑行格式的记录时redo日志类型。

MLOG_COMP_REC_ISNERT(对应的十进制数字为38):表示插入一条使用紧凑行格式的记录时redo日志类型。

(注意:Redundant行格式是比较原始的,非紧凑。而之后新的compact,dynamic,等新的行格式,就是紧凑的,占用内存更小)

MLOG_COMP_PAGE_CREATE(type字段对应的十进制数字为58):表示创建一个存储紧凑行格式记录的页面redo日志类型。

MLOG_COMP_REC_DELETE(type字段对应的十进制数字为42):表示删除一个存储紧凑行格式记录的redo类型日志。

MLOG_COMP_LIST_START_DELTE(type字段对应的十进制数字为44):表示从某条记录开始删除页面一系列使用紧凑行格式记录的redo类型日志。

MLOG_COMP_LIST_END_DELETE(type字段对应的是十进制数字为33):和start首位呼应使用,表示删除的一系列记录结束的位子。

(注意:我们前面说过,数据页存储的数据是按主键索引从小到大顺序排序的,所以我们如果删除连续的数,一个个记录效率很低,所以直接记录删除的头部和删除的尾部就好)


MLOG_ZIP_PAGE_COMPRESS(type表示对应十进制的51):表示压缩一个数据页的redo日志类型。

等等其他的后面用到在介绍。这些日志都包含了物理层面的意思,也包含了逻辑层面的意思,具体指:

物理层面:这些日志都记录了对哪个

逻辑层面:在系统崩溃重启的时候,并不能吧这些日志直接记载,而是先要执行一些函数,需要调用一些事先准备好的函数,在吧页面恢复成系统崩溃时的样子。

我们直接用MLOG_COMP_REC_INSERT插入一条紧凑行格式记录为例子,查看一下他的redo日志结构:

type:

spaceID:

page_number:

n_fields:该条记录代表有多少个字段。

n_uniques:决定该记录唯一的字段数量。

field1_len:

fileld2_len:

等等fileldn_len:这些统一代表各个字段占用存储空间的大小。

Offset:前一条记录的地址。

End_Seg_len:从当前字段可以计算出当前记录占用存储空间的大小。

Info_bits:表示记录头信息前4个比特位的值以及recore_type的值。

Extra_size:记录的额信息占用空间的大小。

Mismatch_index:为了节省redo日志大小而设立的字段,可忽略。

记录的真是数据。

而MLOG_COMP_REC_INSERT的redo日志有点需要注意的是:


我们前面说过,在数据页里,无论是叶子节点还内节点,都是按索引列从小到大排序的。对于二级索引来说,索引列值相同时,记录还需要按主键进行排序。N_Uniques代表该记录,需要几个字段才能确定唯一性,这样插入一条记录时,就可以按照之前的n_uniques个字段进行排序。对于聚簇索引来说,n_uniques的值代表主键的列数,对于其他耳机索引来说,n_uniques代表二级索引列数+主键列数。这里需要注意,唯一二级索引可能为null,该值仍然为索引列数+主键列数。

Field1_len~fieldn_len代表着该记录若干字段占用存储空间的大小,需要注意的是,这里不管是该字段类型是固定长度(int)还是可变长度varchar的,该字段占用的大小都要写入redo日志。

Offset代表该记录的前一条记录页面中的地址。为啥要记录前一条地址呢?为啥要记录前一个页面的地址呢,因为每新增一个记录,都需要修改头记录里的next recored的属性,所以插入新的数据,需要修改上一条记录的next recored属性,方便组成单向链表。

我们前面说过一条记录由额外数据和真实数据组成,这两部分占用的大小,就是一条记录占用空间的总大小。通过end_seg_len的值可以间接计算出一条记录占用的总大小,为啥不直接记录一条记录占用的总大小呢?因为为了节省存储空间,这里面计算的稍微有点复杂,总之就是为了节省redo日志占的大小。

(额外数据包含变长字段长度列表,null值列表,头部信息,后面就是真实数据,compact行如果发生数据存储溢出,真实数据列表会存储一部分真实数据,之后存储的就是指向页的页号,dynamic则在真实数据列表存储的全部都是指向页的页号)

Mismatch_index值也是为了节省redo日志大小而设立的。

很显然,MLOG_COMP_REC_INSERT并没有记录page_n_dir_slots的值修改了啥,page_heap_top修改了啥,page_n_heap修改了啥,而只是吧页中插入的一条记录必备的要素记录下来,之后系统崩溃时,服务器会调用相关某个页面插入一条记录的那个函数,而redo日志中的那些数据就可以被当做调用函数所需要的参数,在调用完函数后,这些page_n_dir_slots,page_heap_top,page_n_heap等值会恢复到系统崩溃前的样子。

Redo日志小结:上面只是吧redo日志都详细介绍了遍,如果不是为了解析redo日志工具,则没必要研究的透透,上面象征介绍几个类型的redo日志,让大家明白:redo日志会吧事务在执行过程中对数据库所做的修改都记录下来,在系统崩溃的时候,又吧事务所做的任何修改都恢复。(注意:为了节省空间,redo日志会吧数据压缩,比如space_id和page_number一般占用4个字节,但压缩后会更小)

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
107 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
29天前
|
SQL 存储 关系型数据库
美团面试:binlog、redo log、undo log的底层原理是什么?它们分别实现ACID的哪个特性?
老架构师尼恩在其读者交流群中分享了关于 MySQL 中 redo log、undo log 和 binlog 的面试题及其答案。这些问题涵盖了事务的 ACID 特性、日志的一致性问题、SQL 语句的执行流程等。尼恩详细解释了这些日志的作用、所在架构层级、日志形式、缓存机制以及写文件方式等内容。他还提供了多个面试题的详细解答,帮助读者系统化地掌握这些知识点,提升面试表现。此外,尼恩还推荐了《尼恩Java面试宝典PDF》和其他技术圣经系列PDF,帮助读者进一步巩固知识,实现“offer自由”。
美团面试:binlog、redo log、undo log的底层原理是什么?它们分别实现ACID的哪个特性?
|
1月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1620 14
|
2天前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL的撤销日志文件和错误日志文件
本文介绍了MySQL的物理存储结构,重点讲解了InnoDB存储引擎中的撤销日志文件(undo log)和错误日志文件。从MySQL 8.0开始,默认生成两个10MB的undo表空间文件,并支持动态扩容和收缩。错误日志文件记录了MySQL启动、运行、关闭过程中的问题,通过示例展示了如何查看和使用这些日志。
|
1月前
|
SQL 存储 关系型数据库
Mysql主从同步 清理二进制日志的技巧
Mysql主从同步 清理二进制日志的技巧
27 1
|
1月前
|
关系型数据库 MySQL 数据库
DZ社区 mysql日志清理 Discuz! X3.5数据库可以做定期常规清理的表
很多站长在网站日常维护中忽略了比较重要的一个环节,就是对于数据库的清理工作,造成数据库使用量增加必须多的原因一般有2个:后台站点功能开启了家园,此功能现在很少有论坛会用到,但是灌水机会灌入大量垃圾信息致使站长长时间未能发觉;再有就是程序默认的一些通知类表单会存放大量的、对于网站日常运行并无意义的通知信息。
65 2
|
23天前
|
存储 关系型数据库 MySQL
MySQL中的Redo Log、Undo Log和Binlog:深入解析
【10月更文挑战第21天】在数据库管理系统中,日志是保障数据一致性和完整性的关键机制。MySQL作为一种广泛使用的关系型数据库管理系统,提供了多种日志类型来满足不同的需求。本文将详细介绍MySQL中的Redo Log、Undo Log和Binlog,从背景、业务场景、功能、底层实现原理、使用措施等方面进行详细分析,并通过Java代码示例展示如何与这些日志进行交互。
41 0
|
2月前
|
存储 缓存 关系型数据库
redo log 原理解析
redo log 原理解析
40 0
redo log 原理解析
|
7天前
|
SQL 关系型数据库 MySQL
go语言数据库中mysql驱动安装
【11月更文挑战第2天】
20 4
|
5天前
|
SQL 关系型数据库 MySQL
12 PHP配置数据库MySQL
路老师分享了PHP操作MySQL数据库的方法,包括安装并连接MySQL服务器、选择数据库、执行SQL语句(如插入、更新、删除和查询),以及将结果集返回到数组。通过具体示例代码,详细介绍了每一步的操作流程,帮助读者快速入门PHP与MySQL的交互。
14 1