MySQL原理简介—7.redo日志的底层原理

大纲

1.redo日志对事务提交后数据不丢失的意义

2.redo日志文件的构成

3.redo日志会写入到Redo Log Blcok中

4.redo日志如何写入到Redo Log Blcok中

5.Redo Log Buffer解析

6.Redo Log Buffer的刷盘时机

7.undo log回滚日志原理

1.redo日志对事务提交后数据不丢失的意义

(1)redo log保证事务提交后修改的数据不丢失

(2)redo log出现的步骤分析

(3)修改过的缓存页刷盘和redo log刷盘的差别

(1)redo log保证事务提交后修改的数据不丢失

更新完缓存页后,必须要写一条redo log,这样才能记录对数据库的修改。

redo log可以保证事务提交后:如果事务中由增删改SQL更新的缓存页还没刷新到磁盘时MySQL宕机,那么MySQL重启后,就可以把redo log重做一遍,恢复事务在当时更新的缓存页,然后再把缓存页刷新到磁盘。

所以redo log的本质是保证事务提交后,修改的数据绝对不会丢失。

(2)redo log出现的步骤分析

步骤一:

MySQL在执行增删改SQL语句时,都是针对一个表中的某些数据执行的。此时首先会找到这个表对应的表空间,然后找到表空间对应的磁盘文件。接着从磁盘文件里把需要更新的数据所在的数据页从磁盘读取出来,也就是将磁盘上的数据页放到Buffer Pool中的缓存页里。

步骤二:

读取磁盘文件的数据页到Buffer Pool的缓存页后,MySQL就会根据增删改SQL语句对缓存页执行更新操作。

步骤三:

在MySQL更新缓存页时,会更新free链表、会更新flush链表、会更新LRU链表。然后后台有专门的IO线程,不定时根据flush链表、LRU链表,把更新过的缓存页(脏页)刷新回磁盘文件的数据页里。

但这种机制有个漏洞:万一事务里有增删改SQL语句更新了缓存页,事务提交了但还没来得及让IO线程把缓存页刷新到磁盘而MySQL宕机。这时Buffer Pool内存里的数据就会丢失,刚做完的事务更新数据也丢失。但也不可能每次提交一个事务,就把事务更新的缓存页刷新回磁盘文件。因为将缓存页刷新到磁盘文件里,是对磁盘随机写的,性能很差。这会导致数据库的性能和并发能力都很弱。

因此才引入了这个redo log机制。

步骤四:

提交事务时把MySQL对缓存页的修改以日志形式写入redo log日志文件。这种redo log日志的格式大致为:对表空间XX中的数据页XX中的偏移量为XXXX的地方更新了数据XXX。

只要事务提交时将所做修改以日志形式写入redo log,则宕机也没关系。因为重启后可以根据redo log,在Buffer Pool里恢复宕机前的事务更新。

(3)修改过的缓存页刷盘和redo log刷盘的差别

事务提交时把修改过的缓存页刷入磁盘,和事务提交时把所做修改的redo log写入日志文件的差别:

一.如果把修改过的缓存页都刷入磁盘

由于一个缓存页是16K,数据还是比较大的,将其刷入磁盘会比较耗时,且修改的缓存页可能仅有几字节的改动,把完整缓存页刷入磁盘不划算。

二.将缓存页刷入磁盘时是对磁盘进行随机写

这时由于一个缓存页对应的位置可能在磁盘文件的一个随机位置,比如偏移量为45336的地方,所以只能进行性能很差的磁盘随机写。

三.如果是将redo log写入日志文件

由于一行redo log只占几十字节,所以写入磁盘日志文件的速度会很快。其中redo log只包含表空间号、数据页号、磁盘文件偏移量、更新值。此外将redo log写入日志时是对磁盘进行顺序写,速度也很快。其中每次进行顺序写时都是直接追加到磁盘文件尾部的。所以提交事务时,使用数据量少 + 顺序写的redo log记录所做的修改,性能会远超直接刷新缓存页到磁盘,这可以让数据库的并发能力更强。

2.redo日志文件的构成

redo log日志本质上记录的是:对某个表空间的某个数据页的某个偏移量的地方修改了几个字节的值。

所以redo日志需要记录的就是:表空间号 + 数据页号 + 偏移量 + 修改几个字节的值 + 具体的值。

根据修改了数据页里多少个字节的值,redo日志类型就可以分为几种:MLOG_1BYTE类型的日志指的是修改了1个字节的值;MLOG_2BYTE类型的日志指的是修改了2个字节的值;以此类推,有修改了4个字节的值的日志类型,还有修改了8个字节的值的日志类型。当然,如果修改了一大串的值,就是MLOG_WRITE_STRING类型。

因此,一条redo日志的结构大致如下:

日志类型(类似MLOG_1BYTE之类的),表空间ID,数据页号,数据页中的偏移量,具体修改的数据

通过这样的日志,MySQL可以知道:这次增删改操作修改了多少字节的数据,在哪个表空间操作的,在哪个数据页执行的,在哪个偏移量开始的,具体的更新数据又是多少。由此就可以精准完美地还原出一次数据增删改操作做的变动了。

如果是MLOG_WRITE_STRING类型的日志,因为不知道修改了多少字节的数据,所以会多一个修改数据长度,格式如下:

日志类型(类似MLOG_1BYTE之类的),表空间ID,数据页号,数据页中的偏移量,修改数据长度,具体修改的数据

3.redo日志会写入到Redo Log Blcok中

每条redo日志记录的是:表空间号 + 数据页号 + 偏移量 + 修改几个字节的值 + 具体的值。

实际写入磁盘时,Redo Log并不是按上述格式写入到日志文件里的。MySQL内有一个数据结构,叫做Redo Log Blcok。redo日志是用一个Redo Log Blcok来存放多个单行日志的。

一个Redo Log Block是512字节,分为3个部分:一是12字节的Header,二是496字节的Body,三是4字节的Trailer。

其中,12字节的Header头又分为4个部分:

一.4个字节的Block No,就是块唯一编号;

二.2个字节的Data Length,就是Block里写入了多少字节数据;

三.2个字节的First Record Group,每个事务都会有多个Redo Log,对应于一组Redo Log。在该Block里的第一组Redo Log的偏移量,就是这2个字节;

四.4个字节的Checkpoint NO;

从上图可知:每个redo日志都是写入到文件里的一个Redo Log Block里去的,一个Redo Log Block最多放496字节Redo Log日志。

4.redo日志如何写入到Redo Log Blcok中

往一个文件里写数据,可认为是从第一行开始从左往右写,会有很多行。

假设现在要写第一条redo日志:首先会把该日志数据放到内存中的一个叫Redo Log Block的数据结构里,然后不断往这个Redo Log Block的数据结构添加一条条redo日志,直到内存里的这个Redo Log Block满了,已经达到512字节。当一个Redo Log Block满时,再一次性把它写入到磁盘文件。

下图展示了redo log和Redo Log Block的关系:

写文件时可以按照字节,一个字节一个字节地写入。文件里存放的东西就是很多很多字节,依次排开。其中可能512个字节会组合起来,就固定代表一个Redo Log Block。这也是中间件系统、数据库系统,底层依赖磁盘文件存储数据的原理。

在磁盘文件的末尾不停写入字节数据,就是磁盘顺序写。在磁盘文件的某个位置找到一个Redo Log Block去修改几个字节的数据,就是磁盘随机写。

5.Redo Log Buffer解析

已知MySQL执行完增删改的SQL语句后:会先让redo日志进入Redo Log Block,然后再写入磁盘的redo日志文件。

Redo Log Buffer是MySQL启动时向操作系统申请的一块连续内存空间。Buffer Pool也是MySQL启动时向操作系统申请的一块连续内存空间。Buffer Pool会在申请内存后划分很多空的缓存页和一些链表结构。Redo Log Buffer也会在申请内存后,划分很多空的Redo Log Block。

innodb_log_buffer_size可配置Redo Log Buffer的大小,默认是16MB。其实16MB已经够大了,毕竟一个Redo Log Block才512字节,每条redo日志也就几个到几十个字节而已。

从Redo Log Buffer结构可知:当要写一条redo日志时,就会从第一个Redo Log Block开始写入。写满了一个Redo Log Block,就会继续写下一个Redo Log Block。以此类推,直到所有Redo Log Block写满。当Redo Log Buffer里的所有Redo Log Block都被写满后,此时就会强制把Redo Log Block刷入到磁盘中。

当一个Redo Log Block满512字节后,也会被追加到redo日志文件里。然后在磁盘文件里不停地追加一个又一个的Redo Log Block。

此外,MySQL平时执行一个事务的过程中,每个事务都会有多个增删改操作,这样就会有多条redo日志。这多条redo日志就是一组Redo Log Group,每次一组Redo Log Group都先在别的地方暂存,执行完后再把一组redo日志写到Redo Log Block里。

如果一组Redo Log Group中的redo日志太多,那么就可能会将其存放在两个Redo Log Block中。如果一组Redo Log Group比较小,那么也可能多个Redo Log Group是在一个Redo Log Block里。

6.Redo Log Buffer的刷盘时机

(1)Redo Log Block什么时候刷盘

(2)redo log日志刷盘的场景

(3)磁盘上到底有几个redo日志文件

(1)Redo Log Block什么时候刷盘

时机一:Redo Log Buffer已使用过半时

如果Redo Log Buffer的日志已占据Redo Log Buffer总容量16M的一半,即超过了8MB的redo日志在缓冲里,此时就会把它们刷入磁盘文件中。

时机二:事务被提交时

一个事务提交时,要把其redo日志所在的Redo Log Block刷入磁盘文件。这样它修改的数据才不会丢失,随时可通过redo日志恢复事务所做修改。

(innodb_flush_log_at_trx_commit的值为1)

时机三:后台线程定时刷新

有个后台线程会每秒把Redo Log Buffer的Redo Log Block刷到磁盘文件。

时机四:关闭MySQL时

当关闭MySQL时,Redo Log Buffer的Redo Log Block都会刷入到磁盘里。

(2)redo log日志刷盘的场景

场景一:

MySQL瞬间执行了大量高并发SQL,1秒就产生了超过8MB的redo日志。此时这些redo日志占据了Redo Log Buffer的一半空间,于是就会刷盘。

这种redo日志刷盘,在MySQL承受高并发请求时是比较常见的。比如每秒执行上万个增删改SQL,每个SQL的redo日志有几百个字节。此时是可能1s生成超8MB的redo日志的,从而触发刷新redo日志到磁盘。但是这种高并发请求的情况一般不常见。

场景二:

正常情况执行一个事务,一般会在几十毫秒到几百毫秒间执行完毕。通常MySQL单事务性能一般不会超过1秒,否则就太慢了。所以执行完一个事务,也会马上把这个事务的redo日志刷入磁盘。这种情况则比较常见,当一个短事务提交时往往会发生redo日志刷盘。

场景三:

后台线程每秒自动刷新redo日志到磁盘去。

总而言之:

一个事务执行时,事务对应的redo日志都进入到Redo Log Buffer。一个事务提交时,事务对应的redo日志刷入磁盘文件才算事务提交成功。这样才能确保事务提交后,数据不会丢,只要有redo日志在磁盘里就行。

(3)磁盘上到底有几个redo日志文件

大量的redo日志是否都放在一个文件里,磁盘空间是否会越占越多?默认情况下,redo log都会写入一个目录中的文件里。这个目录可通过show variables like 'datadir'来查看,可通过innodb_log_group_home_dir参数来进行设置。

redo日志文件是有多个的,写满了一个就会写下一个redo日志文件。可以限制redo日志文件的数量。通过innodb_log_file_size可指定每个redo日文件的大小,默认48MB;通过innodb_log_files_in_group可指定redo日志文件的数量,默认2个。

所以,默认情况下,目录里就两个日志文件,分别为ib_logfile0和ib_logfile1,每个48MB。先写满第一个再进行写满第二个,一个写满了交替覆盖式去写另外一个。

因此,MySQL最多只保留最近的96MB的redo日志而已。事实上这已足够多了,毕竟一条redo log通常就几个字节到几十个字节,96MB已足够存储上百万条redo log了。

7.undo log回滚日志原理

(1)redo log应对的场景—事务提交数据丢失

(2)undo log应对的场景—进行事务回滚

(3)undo log回滚日志的作用

(4)undo log日志长什么样

(1)redo log应对的场景—事务提交数据丢失

已知对Buffer Pool里的缓存页执行增删改操作时,必须要写对应的redo log日志记录下要做的哪些修改。redo log日志都会先进入Redo Log Buffer中的一个Redo Log Blcok,然后事务提交时会将Redo Log Block刷入到磁盘的redo日志文件里。

万一事务已提交,而事务修改的缓存页还没刷入磁盘上的数据页文件。此时MySQL宕机,那么Buffer Pool里被事务修改过的数据就全部丢失。

但只要有redo log,MySQL重启后又可以把那些还没刷入磁盘的缓存页它们所对应的redo log都加载出来,在Buffer Pool的缓存页里重做一遍,这样就可以保证事务提交之后,修改的数据绝对不会丢。

(2)undo log应对的场景—进行事务回滚

假设现在在一个事务里要执行一些增删改操作:那么需要先把对应的数据页从磁盘加载出来放到Buffer Pool的缓存页,然后在缓存页进行增删改,同时记录redo log。

但万一这个事务里的增删改操作执行了一半时,需要进行事务回滚。比如一个事务里有4个增删改操作,结果已经执行了2个增删改SQL。已经更新了一些Buffer Pool的数据,但还有2个增删改SQL还没执行。此时需要回滚事务该怎么处理?

此时如果要回滚事务,就必须对已经在Buffer Pool缓存页里执行过的增删改SQL操作进行回滚。所以才必须引入另外一种日志,就是undo log回滚日志。

这个回滚日志,记录的东西非常简单;

一.比如在缓存页里执行了一条insert语句

那么记录该操作的回滚日志就必须有一个主键和一个对应的delete操作;

二.比如在缓存页里执行了一条delete语句

那么需要记录删除的语句,回滚时就可以执行一条insert操作恢复数据;

三.比如在缓存页里执行了一条update语句

那么需要记录更新前的值,回滚时就可以把更新前的值更新回去;

四.如果执行的是select语句

那么由于select语句并没有在Buffer Pool里执行修改,故无需undo log;

(3)undo log回滚日志的作用

执行事务时,很多insert、update和delete语句都在更新缓存页的数据。如果事务回滚,需要根据每条SQL对应的undo log回滚日志恢复数据。

比如执行了INSERT语句:

则undo log必须记录插入数据的主键ID,回滚时从缓存页里把数据删除;

比如执行了DELETE语句:

则undo log必须记录被删除的数据,回滚时才可以重新插入一条数据;

如果执行了UPDATE语句:

则undo log必须记录修改之前的数据,回滚时才可以把数据更新回去;

(4)undo log日志长什么样

undo log日志里的内容为:这条日志的开始位置、主键的各列长度和值、表ID、undo log日志编号、undo log日志类型、这条日志的结束位置。

其中主键的各列长度和值,指这条数据的主键的每个列的长度和值是多少。如果表没有设置主键,MySQL会设置一个隐藏字段row_id作为表的主键。undo log日志类型,比如INSERT语句的就是TRX_UNDO_INSERT_REC。

#牛客创作赏金赛#
MySQL底层原理与应用 文章被收录于专栏

MySQL底层原理与应用

全部评论

相关推荐

11-10 21:08
已编辑
西安工程大学 前端工程师
点赞 评论 收藏
分享
头像
11-10 15:47
东北大学 Java
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务