关于mysql:日志Redo-Log-和-Undo-Log

3次阅读

共计 8407 个字符,预计需要花费 22 分钟才能阅读完成。

本篇文章次要介绍 Redo Log 和 Undo Log:

  1. 利用 Redo Log 和 Undo Log 实现本地事务的原子性、持久性
  2. Redo Log 的写回策略
  3. Redo Log Buffer 的刷盘机会

日志:Redo Log 和 Undo Log · 语雀 (yuque.com)

通过写入日志来保障原子性、持久性是业界的支流做法。

介绍 Redo Log 和 Undo Log

Redo Log 是什么:Redo Log 被称为重做日志。

Undo Log 是什么:Undo Log 被称为撤销日志、回滚日志。

技术是为了解决问题而生的,通过 Redo Log 咱们能够实现解体复原,避免数据更新失落,保障事务的持久性。也就是说,在机器故障复原后,零碎依然可能通过 Redo Log 中的信息,长久化曾经提交的事务的操作后果。

技术是为了解决问题而生的,Undo Log 的作用 / 性能:

  • 事务回滚 :能够对提前写入的数据变动进行擦除,实现事务回滚,保障事务的原子性。
  • 实现 MVCC 机制 :Undo Log 也用于实现 MVCC 机制,存储记录的多个版本的 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”在解体复原时,会经验以下三个阶段:

  • 分析阶段(Analysis):该阶段从最初一次检查点(Checkpoint,可了解为在这个点之前所有应该长久化的变动都已平安落盘)开始扫描日志,找出所有没有 End Record 的事务,组成待复原的事务汇合(个别包含 Transaction Table 和 Dirty Page Table)。
  • 重做阶段(Redo):该阶段根据分析阶段中,产生的待复原的事务汇合来重演历史(Repeat History),找出所有蕴含 Commit Record 的日志,将它们写入磁盘,写入实现后减少一条 End Record,而后移除出待复原事务汇合。
  • 回滚阶段(Undo):该阶段解决通过剖析、重做阶段后残余的待复原事务汇合,此时剩下的都是须要回滚的事务(被称为 Loser),依据 Undo Log 中的信息回滚这些事务。

MySQL 中一条 SQL 更新语句的执行过程

以下的执行过程限定在,应用 InnoDB 存储引擎的表

  1. 事务开始
  2. 申请加锁:表锁、MDL 锁、行锁、索引区间锁(看状况加哪几种锁)
  3. 执行器找存储引擎取数据。
    1. 如果记录所在的数据页原本就在内存(innodb_buffer_cache)中,存储引擎就间接返回给执行器;
    2. 否则,存储引擎须要先将该数据页从磁盘读取到内存,而后再返回给执行器。
  4. 执行器拿到存储引擎给的行数据,进行更新操作后,再调用存储引擎接口写入这行新数据。
  5. 存储引擎将回滚须要的数据记录到 Undo Log,并将这个更新操作记录到 Redo Log,此时 Redo Log 处于 prepare 状态。并将这行新数据更新到内存(innodb_buffer_cache)中。同时,而后告知执行器执行实现了,随时能够提交事务。
  6. 手动事务 commit:执行器生成这个操作的 Binary Log,并把 Binary Log 写入磁盘。
  7. 执行器调用存储引擎的提交事务接口,存储引擎把刚刚写入的 Redo Log 改成 commit 状态。
  8. 事务完结

其中第 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,这就是 ” 两阶段提交 ”。

“两阶段提交”的作用 / 目标:

  • 将事务设置为 prepare,为了在解体重启时,可能晓得事务的状态
  • 保障两份日志(Binary Log 和 Redo Log)之间的逻辑统一。

如果先写 Redo Log,再写 Binary Log 或者 先写 Binary Log,再写 Redo Log,写入第一个日志后,如果此时产生了解体,那么第二个日志没有写入,就造成了两个日志的不统一。数据库的状态就有可能和用 Binary Log 复原进去的库的状态不统一。备库利用 Binary Log 进行数据同步,就会呈现主备库数据不统一的问题。具体的解说能够看极客工夫的专栏《MySQL 实战 45 讲》

而应用“两阶段提交”,恪守“解体复原时,判断事务该提交、还是该回滚的规定”,就能够保障两份日志(Binary Log 和 Redo Log)之间的逻辑统一。

“解体复原时,判断事务该提交、还是该回滚的规定”如下:

  1. 如果 Redo Log 外面的事务是残缺的,也就是曾经有了 commit 标识,那么利用该 Redo Log 中的信息,长久化事务的操作后果;
  2. 如果 Redo Log 外面的事务只有残缺的 prepare,则判断对应事务的 Binary Log 是否存在并残缺:
    a. 如果是,利用该 Redo Log 中的信息,长久化事务的操作后果;
    b. 如果否,则回滚事务,依据 Undo 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 配置的选项

  • innodb_log_buffer_size:Redo Log Buffer 的内存大小
  • innodb_log_file_size:单个 Redo Log 文件的空间大小
  • innodb_log_files_in_group:Redo Log 文件的数量(默认值是 2)
  • innodb_log_group_home_dir:Redo Log 文件的存储目录(默认值是 .\,即数据目录)
  • innodb_flush_log_at_trx_commit: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 个:

  • 值为 0:示意每次事务提交时,只是把 Redo Log 存储到内存(Redo Log Buffer)就返回,不关怀写入文件
  • 值为 1:示意每次事务提交时,将 Redo Log Buffer 中的内容写入并同步到文件后能力返回(write + fsync 能力返回,这是参数的默认值)
  • 值为 2:示意每次事务提交时,只是把 Redo Log Buffer 中的内容写入内核缓冲区,但不对文件进行同步,何时同步由操作系统来决定(write,fsync 的机会由操作系统决定)

Redo Log 文件组

MySQL 的数据目录(应用 show variables like ‘datadir’ 查看)下默认有两个名为 ib_logfile0 和

ib_logfile1 的文件,Redo Log Buffer 中的 Redo Log 默认状况下就是刷新到这两个磁盘文件中。

数据目录的地位也能够通过以下命令查看:select @@datadir;


如果咱们对默认的 Redo Log 文件组不称心,能够通过下边几个启动参数来调节:

  • innodb_log_buffer_size:每个 Redo Log 文件的空间大小
  • innodb_log_file_size:每个 Redo Log 文件的最大空间大小
  • innodb_log_files_in_group:Redo Log 文件组中所有 Redo Log 文件的数量(默认值是 2,最大值是 100)
  • innodb_log_group_home_dir:Redo Log 文件的存储目录(默认值是 .\,即 MySQL 的数据目录)

从上边的形容中能够看到,磁盘上的 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 都有下边这种通用的构造:

各个局部的具体释义如下:

  • type:该条 Redo Log 的类型
  • space ID:表空间 ID
  • page number:页号
  • data:该条 Redo Log 的具体内容

在 MySQL 5.7.21 这个版本中,InnoDB 的设计者一共为 Redo Log 设计了 53 种不同的类型。各种类型的 Redo Log 的不同之处在于 data 的具体构造不同。

Redo Log 的具体格局能够看掘金小册《MySQL 是怎么运行的:从根儿上了解 MySQL》

这些类型的 Redo Log 既蕴含 物理 层面的意思,也蕴含 逻辑 层面的意思,具体指:

  • 物理层面看,这些日志都指明了对哪个表空间的哪个页进行了什么批改。
  • 逻辑层面看,在零碎奔溃重启时,并不能间接依据 Redo Log 中的信息,将页面内的某个偏移量处复原成某个数据,而是须要调用一些当时筹备好的函数,执行完这些函数后才能够将页面复原成零碎奔溃前的样子。

总结来说,Redo Log 中记录的是该操作对哪个表空间的哪个页的哪个偏移量进行了什么批改。

Mini-Transaction

一个事务可能蕴含多条 SQL 语句,每一条 SQL 语句可能蕴含多个「对底层页面的操作」,每个「对底层页面的操作」可能蕴含多个 Redo Log。这样的一个「对底层页面的操作」的过程被称为 Mini-Transaction,简称 mtr。

「对底层页面的操作」比如说:

  • 向聚簇索引对应的 B+ 树的某个页面中插入一条记录,插入一条记录这个操作可能蕴含多个 Redo Log

咱们须要保障一个「对底层页面的操作」对应的多个 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 时,才认为解析到了一组残缺的 Redo Log,才会进行复原。
  • 否则的话间接放弃前边解析到的 Redo Log。

Redo Log Buffer

Redo Log Buffer 就是在服务器启动时,向操作系统申请的大一片间断的内存空间。

这片间断的内存空间被划分为若干个间断的用来存储 Redo Log 的数据页。

用来存储 Redo Log 的数据页被称为 Redo Log Block。

咱们能够通过启动参数 innodb_log_buffer_size 来指定 Redo Log Buffer 的大小。

  • LOG_BLOCK_CHECKPOINT_NO:示意 checkpoint 的序号

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 刷新到磁盘中(Redo Log 是程序写入的,因而在将某个脏页对应的 Redo Log 从 Redo Log Buffer 刷新到磁盘时,也会保障将在其之前产生的 Redo Log 也刷新到磁盘中。
  • 后盾线程不停的执行刷盘操作
  • 失常敞开服务器时,执行刷盘操作
  • 做 checkpoint 时,执行刷盘操作

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,而是数据,因而它的治理和落盘都跟数据是一样的:

  • Undo 的磁盘构造并不是程序的,而是像数据一样按 Page 治理
  • Undo 写入时,也像数据一样产生对应的 Redo Log
  • Undo 的 Page 也像数据一样缓存在 Buffer Pool 中,跟数据 Page 一起做 LRU 换入换出,以及刷脏。Undo Page 的刷脏也像数据一样要等到对应的 Redo 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/questio…
起源:知乎

Undo Log 配置的选项

  • innodb_max_undo_log_size:值为,单个 Undo Log 最大可占用的字节存储空间;单位为,字节(默认值是 1 G)。
  • innodb_undo_directory:Undo Log 文件的存储目录(默认值是 .\,即数据目录)示意回滚日志的存储目录是数据目录,数据目录的地位能够通过查问变量“datadir”来查看。
  • innodb_undo_log_encrypt:Undo Log 是否加密(默认值是 off,即不加密)。
  • innodb_undo_log_truncate:Undo Log 是否主动截断回收(默认值是 on,即主动截断回收)。这个变量无效的前提是设置了应用独立表空间。
  • innodb_undo_tablespaces:值为 Undo Log 的独立表空间的数量(默认值是 0,即 Undo Log 没有独立的表空间,默认记录到共享表空间 ibdata 文件中)

参考资料

20 | 日志(下):系统故障,如何复原数据?(geekbang.org)

MySQL 是怎么运行的:从根儿上了解 MySQL – 小孩子 4919 – 掘金课程 (juejin.cn)

02 | 日志零碎:一条 SQL 更新语句是如何执行的?- 极客工夫 (geekbang.org)

(9 条音讯) 对于 Innodb undo log 的刷新机会?– 知乎 (zhihu.com)

正文完
 0