ToB企服应用市场:ToB评测及商务社交产业平台

标题: 日志:Redo Log 和 Undo Log [打印本页]

作者: 南飓风    时间: 2022-9-17 08:38
标题: 日志:Redo Log 和 Undo Log
本篇文章主要介绍 Redo Log 和 Undo Log:
日志:Redo Log 和 Undo Log · 语雀 (yuque.com)
通过写入日志来保证原子性、持久性是业界的主流做法。
介绍 Redo Log 和 Undo Log

Redo Log 是什么:Redo Log 被称为重做日志。
Undo Log 是什么:Undo Log 被称为撤销日志、回滚日志。
技术是为了解决问题而生的,通过 Redo Log 我们可以实现崩溃恢复,防止数据更新丢失,保证事务的持久性。也就是说,在机器故障恢复后,系统仍然能够通过 Redo Log 中的信息,持久化已经提交的事务的操作结果。
技术是为了解决问题而生的,Undo Log 的作用 / 功能:
Undo Log 中存储了回滚需要的数据。在事务回滚或者崩溃恢复时,根据 Undo Log 中的信息对提前写入的数据变动进行擦除。
Redo Log 和 Undo Log 都是用于实现事务的特性,并且都是在存储引擎层实现的。由于只有 InnoDB 存储引擎支持事务,因此只有使用 InnoDB 存储引擎的表才会使用 Redo Log 和 Undo Log。
实现本地事务的原子性、持久性

“Write-Ahead Log”日志方案

MySQL 的 InnoDB 存储引擎使用“Write-Ahead Log”日志方案实现本地事务的原子性、持久性。
“提前写入”(Write-Ahead),就是在事务提交之前,允许将变动数据写入磁盘。与“提前写入”相反的就是,在事务提交之前,不允许将变动数据写入磁盘,而是等到事务提交之后再写入。
“提前写入”的好处是:有利于利用空闲 I/O 资源。但“提前写入”同时也引入了新的问题:在事务提交之前就有部分变动数据被写入磁盘,那么如果事务要回滚,或者发生了崩溃,这些提前写入的变动数据就都成了错误。“Write-Ahead Log”日志方案给出的解决办法是:增加了一种被称为 Undo Log 的日志,用于进行事务回滚。
变动数据写入磁盘前,必须先记录 Undo Log,Undo Log 中存储了回滚需要的数据。在事务回滚或者崩溃恢复时,根据 Undo Log 中的信息对提前写入的数据变动进行擦除。
“Write-Ahead Log”在崩溃恢复时,会经历以下三个阶段:
MySQL 中一条 SQL 更新语句的执行过程

以下的执行过程限定在,使用 InnoDB 存储引擎的表
其中第 5 步,将这个更新操作记录到 Redo Log。生成的 Redo Log 是存储在 Redo Log Buffer 后就返回,还是必须写入磁盘后才能返回呢?
这就是 Redo Log 的写入策略,Redo Log 的写入策略由 innodb_flush_log_at_trx_commit 参数控制,该参数不同的值对应不同的写入策略。
还有第 6 步,把  Binary Log 写入磁盘和 Redo Log 一样,也有相应的写回策略,由参数 sync_binlog 控制。
通常我们说 MySQL 的“双 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都设置成 1。也就是说,一个事务提交前,需要等待两次刷盘,一次是 Redo Log 刷盘(prepare 阶段),一次是 Binary Log  刷盘。
Redo Log 的两阶段提交 & 崩溃恢复

在上面【MySQL 中一条 SQL 更新语句的执行过程】部分,最后将 Redo Log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交"。
“两阶段提交”的作用 / 目的:
如果先写 Redo Log,再写 Binary Log 或者 先写 Binary Log,再写 Redo Log,写入第一个日志后,如果此时发生了崩溃,那么第二个日志没有写入,就造成了两个日志的不一致。数据库的状态就有可能和用 Binary Log 恢复出来的库的状态不一致。备库利用 Binary Log 进行数据同步,就会出现主备库数据不一致的问题。具体的讲解可以看极客时间的专栏《MySQL实战45讲》

而使用“两阶段提交”,遵守“崩溃恢复时,判断事务该提交、还是该回滚的规则”,就可以保证两份日志(Binary Log 和 Redo Log)之间的逻辑一致。
“崩溃恢复时,判断事务该提交、还是该回滚的规则”如下:
如果事务写入 Redo Log 处于 prepare 阶段之后、写 Binary Log 之前,发生了崩溃(也就是时刻 A 发生了崩溃),由于此时 Binary Log 还没写,Redo Log 也还没处于 commit 状态,所以崩溃恢复的时候,这个事务会回滚。这时 Binary Log 还没写,所以也不会传到备库。主库和备库的数据状态一致。
如果事务写入 Binary Log 之后,Redo Log 还没处于 commit 状态之前,发生了崩溃(也就是时刻 B 发生了崩溃),根据崩溃恢复时的判断规则中第 2 条,Redo Log 处于 prepare 阶段,Binary Log 完整,所以崩溃恢复的时候,会利用该 Redo Log 中的信息,持久化事务的操作结果。这时 Binary Log 已经写了,所以会传到备库。主库和备库的数据状态一致。
Binary Log 的写入在崩溃恢复时,判断事务该提交还是该回滚时,起到了至关重要的作用,只有 Binary Log 写入成功才能保证两份日志(Binary Log 和 Redo Log)之间的逻辑一致,才能考虑提交。
Redo Log 配置的选项

Redo Log 的写入策略

我们在【MySQL 中一条 SQL 更新语句的执行过程】部分的第 5 步中说:存储引擎将这行新数据更新到内存(innodb_buffer_cache)中。同时,将这个更新操作记录到 Redo Log,此时 Redo Log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
生成的 Redo Log 是存储在 Redo Log Buffer 后就返回,还是必须写入磁盘后才能返回呢?这就是 Redo Log 的写入策略。Redo Log 的写入策略由 innodb_flush_log_at_trx_commit 参数控制。
我们可以通过修改该参数的值,设置 Redo Log 的写入策略,该参数可选的值有 3 个:
Redo Log 文件组

MySQL 的数据目录(使用 show variables like 'datadir' 查看)下默认有两个名为 ib_logfile0 和
ib_logfile1 的文件,Redo Log Buffer 中的 Redo Log 默认情况下就是刷新到这两个磁盘文件中。
数据目录的位置也可以通过以下命令查看:select @@datadir;


如果我们对默认的 Redo Log 文件组不满意,可以通过下边几个启动参数来调节:
从上边的描述中可以看到,磁盘上的 Redo Log 文件不只一个,而是以一个 日志文件组 的形式出现的。这些文件
以 ib_logfile[数字] ( 数字 可以是 0 、 1 、 2 ...)的形式进行命名。
在将 Redo Log 写入 日志文件组 时,是从 ib_logfile0 开始写,如果 ib_logfile0 写满了,就接着 ib_logfile1 写,同理, ib_logfile1 写满了就去写 ib_logfile2 ,依此类推。
如果写到最后一个文件该怎么办呢?那就重新转到 ib_logfile0 继续写,所以整个过程如下图所示:

总共的 Redo Log 文件空间大小其实就是:innodb_log_file_size × innodb_log_files_in_group 。(单个文件的空间大小 * 文件组中的文件个数)
如果采用循环使用的方式向 Redo Log 文件组里写数据的话,那就会造成追尾,也就是后写入的 Redo Log 覆盖掉前边写的 Redo Log。为了解决 Redo Log 的覆盖写入问题,InnoDB 的设计者提出了 checkpoint 的概念。
Redo Log 写入 Redo Log Buffer

Redo Log 的格式

InnoDB 的设计者为 Redo Log 定义了多种类型,以应对事务对数据库的不同修改场景,但是绝大部分类型的 Redo Log 都有下边这种通用的结构:

各个部分的详细释义如下:
在 MySQL 5.7.21 这个版本中,InnoDB 的设计者一共为 Redo Log 设计了 53 种不同的类型。各种类型的 Redo Log 的不同之处在于 data 的具体结构不同。
Redo Log 的具体格式可以看掘金小册《MySQL 是怎样运行的:从根儿上理解 MySQL》
这些类型的 Redo Log 既包含 物理 层面的意思,也包含 逻辑 层面的意思,具体指:
总结来说,Redo Log 中记录的是该操作对哪个表空间的哪个页的哪个偏移量进行了什么修改。
Mini-Transaction

一个事务可能包含多条 SQL 语句,每一条 SQL 语句可能包含多个「对底层页面的操作」,每个「对底层页面的操作」可能包含多个 Redo Log。这样的一个「对底层页面的操作」的过程被称为 Mini-Transaction,简称 mtr。
「对底层页面的操作」比如说:
我们需要保证一个「对底层页面的操作」对应的多个 Redo Log 不可分割,即一个「对底层页面的操作」是原子的,这个操作对应的 Redo Log 要么都写入磁盘,要么都不写入磁盘。所以 InnoDB 的设计者规定在执行这些需要保证原子性的操作时必须以 组 的形式来记录 Redo Log,在进行奔溃恢复时,针对某个组中的 Redo Log,要么把全部的 Redo Log 都恢复掉,要么一个 Redo Log 也不恢复。
那么 InnoDB 的设计者是怎么做到分组的呢?InnoDB 的设计者在一个「对底层页面的操作」的最后一个 Redo Log 后面加上一个特殊类型的 Redo Log。相当于某个需要保证原子性的操作产生的一系列 Redo Log 必须要以一个特殊类型的 Redo Log 结尾,这样在奔溃恢复时:

Redo Log Buffer

Redo Log Buffer 就是在服务器启动时,向操作系统申请的大一片连续的内存空间。
这片连续的内存空间被划分为若干个连续的用来存储 Redo Log 的数据页。
用来存储 Redo Log 的数据页被称为 Redo Log Block。
我们可以通过启动参数 innodb_log_buffer_size 来指定 Redo Log Buffer 的大小。



Redo Log 写入 Redo Log Buffer

我们前边说过一个 mtr 执行过程中可能产生若干个 Redo Log ,这些 Redo Log 是一个不可分割的组,所以其实并不是每生成一个 Redo Log,就将其插入到 Redo Log Buffer 中,而是每个 mtr 运行过程中产生的日志先暂时存到一个地方,当该 mtr 结束的时候,将过程中产生的一组 Redo Log 再全部复制到 Redo Log Buffer 中。
不同的事务可能是并发执行的,所以不同事务的 mtr 可能是交替执行的。每当一个 mtr 执行完成时,伴随
该 mtr 生成的一组 Redo Log 就需要被复制到 Redo Log Buffer 中,也就是说不同事务的 mtr 可能是交替写入 Redo Log Buffer 的。
Redo Log Buffer 中 Redo Log 的刷盘时机

mtr 运行过程中产生的一组 Redo Log 在 mtr 结束时会被复制到 Redo Log Buffer 中,在一些情况下 Redo Log Buffer 中的 Redo Log 会被写回磁盘,Redo Log 的刷盘时机如下:
Redo Log Buffer 的空间不足时,执行刷盘操作
Redo Log Buffer 的空间是有限的,空间大小由 innodb_log_buffer_size 来指定。
InnoDB 的设计者认为:如果 Redo Log Buffer 的内存被占用 1 / 2,就需要把 Redo Log Buffer 中的 Redo Log 刷新到磁盘。
一个事务提交时,执行刷盘操作
在前面【Redo Log 的写入策略】部分,讲到我们可以通过设置 innodb_flush_log_at_trx_commit 参数的值,在事务提交时执行刷盘操作后才能返回。
事务提交时执行刷盘操作后才能返回是 Redo Log 的默认写入策略。
后台线程不停的执行刷盘操作
后台有一个线程,每秒都会执行一次刷盘操作。后台线程执行刷盘操作的频率可以通过参数设置。
具体通过哪个参数设置,我也不清楚。
正常关闭服务器时,执行刷盘操作
做 checkpoint 时,执行刷盘操作
等等
Undo Log 的写回策略

MySQL中的 Undo Log 严格的讲不是 Log,而是数据,因此它的管理和落盘都跟数据是一样的:
之所以这样实现,首要的原因是 MySQL 中的 Undo Log 不只是承担 Crash Recovery 时保证 Atomic 的作用,更需要承担 MVCC 对历史版本的管理的作用,设计目标是高事务并发,方便的管理和维护。因此当做数据更合适。
但既然还叫 Log,就还是需要有 Undo Log 的责任,那就是保证 Crash Recovery 时,如果看到数据的修改,一定要能看到其对应 Undo 的修改,这样才有机会通过事务的回滚保证 Crash Atomic。标准的 Undo Log 这一步是靠 WAL 实现的,也就是要求 Undo 写入先于数据落盘。而 InnoDB 中 Undo Log 作为一种特殊的数据,这一步是通过 redo 的 min-transaction 保证的,简单的说就是数据的修改和对应的 Undo 修改,他们所对应的 Redo Log 被放到同一个 min-transaction 中,同一个 min-transaction 中的所有 Redo Log 在 Crash Recovery 时以一个整体进行重放,要么全部重放,要么全部丢弃。
作者:CatKang
链接:https://www.zhihu.com/question/267595935/answer/2204949497
来源:知乎
Undo Log 配置的选项


参考资料

20 | 日志(下):系统故障,如何恢复数据? (geekbang.org)
MySQL 是怎样运行的:从根儿上理解 MySQL - 小孩子4919 - 掘金课程 (juejin.cn)
02 | 日志系统:一条SQL更新语句是如何执行的?-极客时间 (geekbang.org)
关于Innodb undo log的刷新时机? - 知乎 (zhihu.com)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4