一文搞懂MySQL之redo日志(含字节面试题)-创新互联

MySQL之redo日志 前言

前几天看到一道面试题,题目是:事务还没提交的时候,redo log 能不能被持久化到磁盘呢?

“只有客户发展了,才有我们的生存与发展!”这是创新互联的服务宗旨!把网站当作互联网产品,产品思维更注重全局思维、需求分析和迭代思维,在网站建设中就是为了建设一个不仅审美在线,而且实用性极高的网站。创新互联对成都网站设计、网站制作、外贸营销网站建设、网站制作、网站开发、网页设计、网站优化、网络推广、探索永无止境。

在学习和了解redo日志前,我就在想,这题目啥玩意啊?一个开发岗都考这么细吗?

后来,学习完redo日志后,发现这个面试题确实挺有意思的。就是考查你对redo日志的了解到底有多深,是停留在表面的理解呢?还是真的修炼到一定程度了,这个题目真的就可以反映出来。

所以,你的答案是什么呢?(答案在文末)

相信读完本篇文章,理解redo日志后,就会有你自己的答案了…

1. 什么是日志?

按照普通字面意思来理解的话,日志是日记中的一种,多指非个人的,一般是记载每天所做的工作。书面日志如"教学日志",“班级日志”,"工作日志"等…

而我们这里所指的日志,则是程序中的日志。对程序的运行过程进行详细的记录;是为了保障系统的正常运行,是在程序出现问题时,方便程序员去查阅日志发现错误的重要手段之一。

1.1 数据库日志

在数据库系统中,对数据的任何更新操作(如:增加、修改、删除),都要把相关操作的命令、执行时间、数据的更新等信息保存下来。这些被保存的信息就是数据库日志。也就是说,数据库日志是数据库系统中所有更新活动的操作序列。

更为重要的是,数据库日志是系统正常运行、保持数据一致性的重要手段。

redo log(重做日志)则是数据库日志中的一种。它支持再写入,恢复提交事务修改的数据页操作,以此来保证数据的持久性。

2. 为什么需要redo日志?

众所周知,事务有四种特性:原子性、一致性、隔离性、持久性。

那么事务的四种特性到底是基于什么机制实现的呢?

  • 事务的隔离性由锁机制实现
  • 事务的原子性、一致性、持久性由事务的redo日志和undo日志来保证。

InnoDB存储引擎是以页为单位来管理存储空间的。在真正访问页面之前需要把在磁盘上的页缓存到内存中的Buffer Pool之后才可以访问。所有的变更都必须先更新缓冲池中的数据,然后缓冲池中的脏页会以一定的频率被刷入磁盘(checkPoint机制),通过缓冲池来优化CPU和磁盘之间的鸿沟,这样就可以保证整体的性能不会下降太快。

但是,由于checkPoint机制并不是在每次修改数据时就将脏页刷入磁盘,这个时候,就怕数据库宕机,一旦宕机,那么缓冲池中的所有脏页都会被清除;就会出现,我们commit亲自提交的事务,数据库竟然没有改变(表情:大惊失色)!

也就是说,事务的持久性失效了!!

那么如何保证事务的持久性呢?这个时候,redo log就应运而生了。

2.1 redo log 的作用

我们只是想让已经提交的事务对数据库中数据所做的修改永久生效,即使系统崩溃,在重启后也能把修改的数据写入数据库中。我们只需要将修改了哪些数据记录一下就好。比如:某个事务将系统表空间中第10号页面中地址偏移量为100处那个字节的值1改为2;则只需要记录:将第0号表空间的10号页面的地址偏移量为100处的值更新为 2 。

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

在这里插入图片描述

3. redo日志的好处、特点 3.1 好处
  • redo日志降低了刷盘频率。若没有redo日志,数据库系统则必须确保每次事务对数据库进行修改时,必须马上同步到磁盘(不管修改数据量的多少)
  • redo日志占用的空间非常小。它只保存没有刷新到磁盘上的操作内容。
3.2 特点
  • redo日志是顺序写入磁盘的。在执行事务的过程中,每执行一条语句,就可能产生若干条redo日志,这些日志是按照产生的顺序写入磁盘的,也就是使用顺序IO,效率比随机IO快。
  • 事务执行过程中,redo log不断记录。
4. redo的组成

redo log可以简单分为以下两个部分:重做日志的缓冲、重做日志文件。

4.1 重做日志的缓冲 (redo log buffer)

在服务器启动时就向操作系统申请了一大片称之为redo log buffer的连续内存空间,翻译成中文就是redo日志缓冲区。这片内存空间被划分成若干个连续的redo log block。一个redo log block占用512字节大小。redo log buffer是属于内存层面,是易丢失的。

在这里插入图片描述

参数设置:innodb_log_buffer_size

redo log buffer 大小,默认16M,大值是4096M,最小值为1M。

mysql>show variables like '%innodb_log_buffer_size%';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+
4.2 重做日志文件(redo log file)

redo log file是保存在硬盘中,是持久化的。

在这里插入图片描述

4.2 redo log的流转过程

以一个更新事务为例,redo log 流转过程,如下图所示:

在这里插入图片描述

  • 第1步:先将原始数据从磁盘中读入内存中(数据库缓冲池)

  • 第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值

  • 第3步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加写的方式

  • 第4步:定期将内存中修改的数据刷新到磁盘中

5.*redo log 的刷盘策略

redo log的写入并不是直接将数据写入磁盘的,InnoDB引擎会在写redo log的时候先写将数据写入redo log buffer,之后以一定的频率刷入到真正的redo log file中。这里的一定频率怎么看待呢?这就是我们要说的刷盘策略。

在这里插入图片描述

注意:redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到文件系统缓存(page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系统自己来决定(比如page cache足够大了时)。

那么对于InnoDB来说就存在一个问题,如果交给系统来进行写入,同样如果系统宕机,那么数据也丢失了(虽然整个系统宕机的概率还是比较小的)。

针对这种情况,InnoDB给出innodb_flush_log_at_trx_commit参数,该参数控制 commit 提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:

  • 值为0时:表示每次事务提交时不进行刷盘操作,都只是把 redo log 留在 redo log buffer 中。(系统默认master thread每隔1s进行一次重做日志的同步)
  • 值为1时:每次事务提交的时候,都执行 fsync (同步) 将 redo log 直接持久化到磁盘( 默认值 )
  • 值为2时:每次事务提交的时候,都只执行 write(写) 将 redo log 写到文件系统的 page cache 中。由OS自己决定什么时候fsync(同步)到磁盘文件(redo log file)。
show variables like 'innodb_flush_log_at_trx_commit';

mysql>show variables like 'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1     |
+--------------------------------+-------+
1 row in set (0.00 sec)

另外,InnoDB存储引擎有一个后台线程(master thread),每隔1秒,就会把redo log buffer中的内容写到文件系统缓存(page cache),然后调用刷盘操作。

在这里插入图片描述

也就是说,一个没有提交事务的redo log记录,也可能会被刷入磁盘。因为在事务执行过程中,只要发生数据的更改,redo log记录是会马上写入到redo log buffer中,这些redo log记录会被后台线程刷盘。所以说,对于上面那个字节的面试题来说。

相信你们已经有答案了。

除了后台线程每秒1次的轮询操作,还有一种情况,当redo log buffer占用的空间即将达到innodb_log_buffer_size(这个参数默认是16M)的一半的时候,也就是redo log buffer的容量达到8M时,后台线程会主动写盘(由于这个事务可能并没有提交,所以这个写盘动作只是 write 到了文件系统的 page cache,仍然是在内存中,并没有调用 fsync 真正落盘)。

6. 重写日志文件(redo log file) 6.1 相关参数设置
  • innodb_log_group_home_dir:指定redo log文件所在的路径,默认值为./。表示在数据库的数据目录下。

    MySQL的默认数据目录(linux):var/lib/mysql;window下则是C:\ProgramData\MySQL\MySQL Server 8.0\Data,文件下有两个名为ib_logfile0ib_logfile1的文件。redo log buffer中的日志默认情况下就是刷新到这两个磁盘文件中。

  • innodb_log_files_in_group:指明redo log file的个数,命名方式如:ib_logfile0,ib_logfile1…ib_logfilen。默认2个,大100个。

    mysql>show variables like 'innodb_log_files_in_group';
    +---------------------------+-------+
    | Variable_name | Value |
    +---------------------------+-------+
    | innodb_log_files_in_group | 2 |
    +---------------------------+-------+
  • innodb_flush_log_at_trx_commit:控制 redo log 刷新到磁盘的策略,默认为1(前面已经介绍过)。

  • innodb_log_file_size:单个 redo log file文件设置大小,默认值为48M。大值为512G,注意大值指的是整个 redo log file系列文件之和,即(innodb_log_files_in_group * innodb_log_file_size )不能大于大值512G。

    mysql>show variables like 'innodb_log_file_size';
    +----------------------+----------+
    | Variable_name | Value |
    +----------------------+----------+
    | innodb_log_file_size | 50331648 |
    +----------------------+----------+
6.2 日志文件组

从上边的描述中可以看到,磁盘上的redo日志文件不只一个,而是可以以一个日志文件组的形式出现的。这些文件以ib_logfile[数字]数字可以是0、1、2…)的形式进行命名,每个的redo日志文件大小都是一样的。

在将redo日志写入日志文件组时,是从ib_logfile0开始写,如果ib_logfile0写满了,就接着ib_logfile1写。同理,ib_logfile1写满了就去写ib_logfile2,依此类推。如果写到最后一个文件该咋办?那就重新转到ib_logfile0继续写,所以整个过程如下图所示:

在这里插入图片描述

总共的redo log file的大小其实就是:innodb_log_file_size × innodb_log_files_in_group(即单个文件大小 * 文件个数)。

有人可能会问,ib_logfile0都已经写完了,怎么最后还可以重新转到ib_logfile0继续写呢?那不就把之前的数据给覆盖了吗?

对于这个问题,InnoDB的设计者早就想到了,并提出了checkPoint的概念。

6.2.1 checkpoint机制

在整个日志文件组中还有两个重要的属性,分别是write pos、checkpoint。

  • write pos是当前记录的位置,也就是当前使用到的位置,一边写一边后移
  • checkpoint是当前要擦除的位置,也是往后推移

每次刷盘redo log记录到日志文件组中,write pos位置就会从0开始后移更新。每次MySQL通过加载日志文件组恢复数据时(也就是通过redo log恢复数据库的数据),则会清空加载过的redo log记录(已经恢复的数据),并把 checkpoint后移更新。write pos和checkpoint之间的还空着的部分可以用来写入新的redo log记录。

在这里插入图片描述

如果 write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log记录,MySQL 得停下来,清空一些记录,把 checkpoint 推进一下。

在这里插入图片描述

7. 总结

本文讲解了redo日志的出现解决了事务的持久性的问题;以及redo日志的好处和它的特点,还分析了redo日志的组成:redo log buffer 和redo log file,并且介绍了它们各自的职责以及相应的作用。

还有比较重要的一点:redo日志的刷盘策略,这对我们之后的数据库调优有一定的帮助和经验。

以及最后redo日志在文件中到底是如何存储的,它的格式、它的运行机制等。

InnoDB的更新操作采用的是Write Ahead Log(预先日志持久化)策略,即先写日志,再写入磁盘。重点掌握redo的作用、组成和刷盘策略等。

在这里插入图片描述

面试答案:

答案是:事务还没有提交的时候,redo log 是有可能被持久化到磁盘的。

redolog 的具体落盘操作是这样的:在事务运行的过程中,MySQL 会先把日志写到 redolog buffer 中,等到事务真正提交的时候,再统一把 redolog buffer 中的数据写到 redo log 文件中。不过这个从 redolog buffer 写到 redo log 文件中的操作也就是 write 并不就是落盘操作了,这里仅仅是把 redolog 写到了文件系统的 page cache 上,最后还需要执行 fsync (同步)才能够实现真正的落盘。

InnoDB 有一个后台线程,每隔 1 秒轮询一次,具体的操作是这样的:调用 write 将 redolog buffer 中的日志写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。而在事务执行中间过程的 redo log 都是直接写在 redolog buffer 中的,也就是说,一个没有提交的事务的 redo log,也是有可能会被后台线程一起持久化到磁盘的。

另外,除了后台线程每秒一次的轮询操作外,还有两种场景会让一个没有提交的事务的 redo log 写盘:

innodb_flush_log_at_trx_commit 设置是 1,这样并行的某个事务提交的时候,就会顺带将这个事务的 redolog buffer 持久化到磁盘

举个例子,假设事务 A 执行到一半,已经写了一些 redo log 到 redo log buffer 中,这时候有另外一个事务 B 提交,按照 innodb_flush_log_at_trx_commit = 1 的逻辑,事务 B 要把 redolog buffer 里的日志全部持久化到磁盘,这时候,就会带上事务 A 在 redolog buffer 里的日志一起持久化到磁盘

redo log buffer 占用的空间达到 redo log buffer 大小(由参数 innodb_log_buffer_size 控制,默认是 48MB)一半的时候,后台线程会主动写盘。不过由于这个事务并没有提交,所以这个写盘动作只是 write 到了文件系统的 page cache,仍然是在内存中,并没有调用 fsync 真正落盘。

具体参考:https://zhuanlan.zhihu.com/p/456411101


写在最后

好了,以上就是本篇文章的全部内容,如果对你有所收获或者启发。希望能点赞+收藏支持一下!

我是胡亦,一名热爱分享技术干货的博主。

我们下次再见!!🙋‍♂️🙋‍♂️

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


网站栏目:一文搞懂MySQL之redo日志(含字节面试题)-创新互联
当前链接:http://ybzwz.com/article/iejgg.html