MySQL 的 binglog、redolog、undolog


mysql 的操作其实包括了多种日志,比如错误日志、查询日志、慢查询日志(执行一条速度很慢的查询时会记录在这里面)等,其中非常重要的有归档日志、事务日志等

binlog 日志

binlog(归档日志,又叫二进制日志):mysql 数据库自带的日志(mysql 分两层,上层是 mysql-server,下层是可替换的数据库引擎),值得一提的是,binlog 是默认关闭的

binlog 的作用

1,这个日志用来归档,也就是数据库的基于时间点的数据还原

2,MySQL 数据库的数据备份、主备、主主、主从都离不开 binlog,需要依靠 binlog 来同步数据,保证数据一致性,这也是它的主要主要——主从复制

3,被 sql 注入后,可以查找就恢复数据

binlog 在磁盘里的结构

binlog 存放的位置由 datadir 参数控制,在 binlog 文件夹下有两种文件,一个是 binlog0000XX,记录了修改操作的逻辑,并且有多个版本,另一个是 binlog-index,记录了所有的 binlog 历史版本名称
在这里插入图片描述
在每次写入操作的时候,将信息以二进制的形式保存在磁盘中,binlog 是 mysql 的逻辑日志

binlog 可以通过 binlog_format 来控制存储格式:

  • row:存储全部行信息,就算只修改一个数据,也会把这个表记录下来,消耗空间大
  • statement:记录 sql 语句的信息,所消耗的空间比较少
  • mixed:会根据情况来自动决定存储格式是 row 还是 statement,不推荐使用

这里说一下逻辑日志和物理日志的区别:

  • 逻辑日志可以简单理解为记录的就是 sql 语句,可能和真正的 sql 语句有些许区别,但是总体上还是大差不差的
  • mysql 数据最终是保存在数据页中的,物理日志记录的就是数据页变更内容

binlog 刷盘时机

事务执行时,会给每个线程在内存中分配一块地方叫 binlog cache,binlog 文件就记录在这里,事务提交的时候,再把 binlog cache 写到 binlog 文件中。注意,一个事务的 binlog 不能被拆开提交,无论这个事务多大,也要确保一次性写入

对于 InnoDB 存储引擎而言,只有在事务提交时才会记录 binlog ,此时记录还在内存中,那么 biglog 是什么时候刷到磁盘中的呢?

mysql 通过 sync_binlog 参数控制 biglog 的刷盘时机,取值范围是0-N:

  • 0:不去强制要求,由系统自行判断何时写入磁盘;
  • 1:每次 commit 的时候都要将 binlog 写入磁盘;
  • N:每执行 N 个事务,才会将 binlog 写入磁盘。

从上面可以看出, sync_binlog 最安全的是设置是 1 ,这也是 MySQL 5.7.7 之后版本的默认值。但是设置一个大一些的值可以提升数据库性能,因此实际情况下也可以将值适当调大,牺牲一定的一致性来获取更好的性能

主从复制

之前也讲过 binlog 是用来做数据一致性的,那具体的步骤是什么呢?

首先,当从库链接主库的时候,主库会有 binlog dump 线程实时监测 binlog 的变更并将这些新的 events 推给从库。此时数据库的连接中会有这个信息:Master has sent all binlog to slave; waiting for more updates

从库有一个 IO 线程专门用来读主库的 event,读到后会生成 relay log(中继日志),从库还有一个SQL thread,SQL thread 用来在从库上执行 relay log

由于异步复制的过程,它在主机挂掉的时候,会有一部分已经提交的事务无法复制到从库。半同步相对异步复制来说,能更少的避免数据丢失,不过性能方面较差,下面额外说一下半同步和全同步:

1,半同步是这个过程,在事务提交之前,将 binlog 发送到从库,然后从库给主库发送确认接受消息,之后主库才进行提交
2,全同步则是主库在确认所有的从库接受到消息之后,主库才会提交,这么做会最大程度上解决主从延迟问题,但是数据库的写入性能也很差。因此一主一从,主写从读全同步,是小型项目中较为不错的数据库集群方案
3,在异步复制中,主库(Master)在将事务提交到本地存储后,不等待从库(Slave)的确认就返回给客户端,这样可能导致数据不一致的问题

主从延迟一遍出现在读后写或者写后读的场景,比如同一个线程在写库之后立即读取,然后使用主写从读的方案,这时候有可能出现数据读不到的问题。再比如一个线程在写入数据之前需要确认数据库中有没有这个数据,因此先查了一遍数据库,但是因为数据库有延迟,其他的线程对 DB 的修改没有被这个线程感知到,因此出现了修改丢失的问题

处理这些情况需要在代码中定义好规范,读后写需要加分布式锁,并且避免写后读的情况

redolog 日志

redolog(重做日志):InnoDB 储存引擎自带,引擎用这个来支持事务,机械故障后根据这个日志进行事务回滚操作。这个日志是事务执行的关键

redolog 日志只记录事务对哪些数据页做了哪些修改,它是物理日志,它会存储类似以下意思的内容,就是位置与数据:

将 x 表空间的 xx 页偏移量为 xxx 的内容修改为 xxxx

这样记录的好处是方便快捷,在尽可能少的数据里表达尽可能多的信息,因此在存入磁盘时非常快速。redolog 的类型有很多,它的每种类型都可能不一样,它不完全是上面这种格式的。但是他们的共同点就是记录修改之前和修改之后的内容,这样的特性导致他们可以快速回滚数据

redolog 的作用

MySQL 为了提高的性能,对于增、删、改这种操作都是在内存中完成的,也就是将 Buffer Pool 中的数据页改成脏数据页,此外 MySQL 还有专门的后台线程等其他机制负责将脏数据页刷新同步回磁盘。这么做是因为 MySQL 以页为单位读取数据,每次修改都进行 IO 那么效率就太低了

那么当脏数据没有刷入磁盘或者正在刷入磁盘时电脑挂了怎么办,也就是事务处于失败状态的时候如何将其回滚到中止状态

redolog 就是用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置),只要在重启时解析 redo log 中的事务然后重做一遍。将 Buffer Pool 中的缓存页重做成脏页。后续再在合适的时机将该脏页刷入磁盘即可

MyIsam 为什么不支持回滚与事务,就是因为存储引擎没有实现 redolog。请想想事务需要满足什么条件,ACID 对吧,该日志的存入磁盘的方式就决定了原子性与持久性这两个功能的实现

  • 原子性靠日志写入磁盘的方式满足,虽然 redolog 向磁盘中写入不是一次性的,在事务执行过程中会不断写入,但是在提交时一定会写入一个特殊标识,即 Commit Record(提交记录)。数据库看到该记录时才会根据日志上的消息或者内存中的页来修改数据,当修改完毕后,会加入一条 End Record(结束记录)表示该事务已经持久化
  • 持久性就是日志本身的特性,这些日志因为在提交时优先写入,因此我们能保证对数据库的修改都被记录在案

其实除了上面这种提交日志来保证原子性与持久性,还有一种叫**Shadow Paging(影子分页)**的方式可以实现这种功能,它的大体思路是在数据写入的时候,先将原来的数据拷贝一份(影子),对影子进行修改,在提交的时候将指向数据的指针指向影子以替换原来的数据。不过这么做在实现隔离性的时候会遇上很多问题,因此只有在小型的数据库中才用这种方式实现(redis 的 AOF 也使用这种方式)

redolog 刷盘时机

redolog 因为非常小,不占用内存空间,落盘比较快,并且以一定规则插入磁盘,因此保证了数据的一致性。binlog 只是在事务提交完成后进行一次写入,而 redo log 则是在事务进行中不断地被写入,这也就是说在 redo log 中针对一个事务会有多个不连续的记录日志

redolog 刷盘时机有以下几种情况:

1,脏页刷盘时 redolog 找时机刷盘,在多线程的情况下,脏页是顺序刷盘的,redolog 也是顺序刷盘的

mysql 每执行一条 DML 语句,会先将记录写入内存中的 redo log buffer,并且根据不同的配置,在不同的时间点再一次性将多个操作记录写到 redo log file,即磁盘中的 redo 文件组

这里的某个时间点,可以根据参数 innodb_flush_log_at_trx_commit 来设置,和 binlog 类似:

  • 设置为1:表示当你 commit 时,MySQL 必须将 rodolog-buffer 中的数据刷新进磁盘中。确保只要 commit 是成功的,磁盘上就得有对应的 rodolog 日志。这也是最安全的情况
  • 设置为0:表示每次事务提交时不进行刷盘操作
  • 设置为2:表示当你 commit 时,将 redolog-buffer 中的数据刷新进 OS Cache 中,然后依托于操作系统每秒刷新一次后台线程,使用该机制将数据同步到磁盘中,也存在丢失的风险

双1设置:始终设置 innodb_flush_log_at_trx_commit=1,启用二进制日志记录并设置 sync_binlog=1,这就是MySQL最安全的设置方法

2,后台线程刷盘,就是上面说的每秒一次的频率将 redolog 刷新到磁盘

3,正常关闭服务器时

4,当 redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动刷盘

基于后台线程以及一半就刷盘的机制,有可能在事务未提交时就会将 redolog 刷入磁盘中

5,做 ckeckpoint 时

一条 redolog 的格式

redolog 有很多类型,不同的类型有不同的格式,大致有以下的通用格式,以及一些相当的格式

最简单的 redolog 里面存储着表空间ID、页号、偏移量以及需要更新的值,因此所需内存是比较小的
在这里插入图片描述
type :该条 redo 日志的类型。
space ID :表空间 ID。
page number :页号。
data :该条 redo 日志的具体内容

这种格式用于内存对磁盘修改非常少的情况下,比如将隐藏主键到达256时写入磁盘时。往往一些增加删除操作需要对磁盘中的页进行大规模修改,比如增加一条数据,不仅仅是在页中插入一条数据行,它还可能修改以下内容:

  • 用于二分查找的槽,这个行加入后,作为“带头大哥”的数据可能会改变
  • 页头中用于统计的各种信息,比如记录数、链表等等
  • 组、区这些用于划分地盘的信息
  • 其他不经常出现的信息

我们在每个修改的地方都记录一条对应的 redo 日志显然是不现实的,因此实现方式是用时间换空间,我们在数据库崩了之后用日志还原数据时,在执行这条日志之前,数据库应该是一个一致性状态,我们用对应的参数,执行固定的步骤,修改对应的数据。这就类似与函数,因此我们只要记录所有的参数即可

这些比较复杂的重写日志类似下面这样
在这里插入图片描述

redolog 的完整性

redolog 也需要保证数据的完整性,不能说 redolog 写一半系统崩溃了,我们还拿着错误的 redolog 去恢复数据库。redolog 虽然比较小,但是还是数据,并且是一段连续的数据,因此和接受 TCP 报文的保证数据完整性的方式都是一样的,在末尾多加了一条特殊形式的 redo 日志 MLOG_MULTI_REC_END,系统读到这条日志则认为这组 redo 日志完整

mini-transaction(MTR)

mysql 规定一些操作是不可分割的最小单元,对这些操作的一次原子访问称为一个 mini-transaction,简称 mtr。这些操作有:

(1)更新 Max Row Id 属性(更新一页)

(2)向聚簇索引对应的 B+ 树的页面插入一条记录(可能更新多个页面)

(3)向某个二级索引对应的 B+ 树的页面插入一条记录(可能更新多个页面)

(4)其他的对页面的访问操作

所以一个 mtr 可能会产生多条 redo 日志,这些 redo 日志可以归为一组,称为 redo 日志记录组(redo log record group)

一条 sql 语句(比如 insert)可能会操作多棵 B+ 树,所以一条sql语句会包含多个 mtr

一个事务可能包含多条 sql 语句。

所以,事务、sql 语句、mtr、redo log 的关系如下:
在这里插入图片描述

redolog 在磁盘里的结构

redolog 在内存中的存放位置叫 log buffer,我们为了了解 redolog 应该在哪个页的哪个偏移量写,提供了一个叫 buf_free 的全局变量,该变量指明后续写入的 redo 日志应该写到 log buffer 的哪个位置

那 redolog 在磁盘中又是如何存储的呢?

硬盘上存储的 redo log 日志文件不只一个,而是以一个日志文件组的形式出现的,每个的 redo 日志文件的大小都是一样的

比如可以配置为一组4个文件,每个文件的大小是 1GB,整个 redo log 日志文件组可以记录 4G 的内容

每个文件中都包含了多个页,并且这些页负责的内容可能不一样,每个文件的前2048个字节(也就是前四页)用来存储一些管理信息,之后的页就是用来存储 redolog 的普通页了

日志文件组采用的是环形数组形式,从头开始写,写到末尾又回到头循环写。为了管理这个日志文件组,我们显然需要一些全局变量,比如记录了当前写在哪里的偏移量,一共写了多少数据等等内容

redolog 相关全局变量

lsn,也就是 log sequence number 这个全局变量是记录当前总共写入的 redo 日志量的。在 mysql 开机时,这个值为 8704(一条数据都没写入 lsn 就是8704)。每次写入 x 字节的数据,这个值就加 x。同时,他还会将遇到的 log block header 和 log block trailer 占用的字节数加上(也就是页头和页尾占用的字节数)

而 buf_free 是下一次写入的记录的偏移量,一边写一边后移,它用来记录当前事务产生的 redo log 文件

flushed_to_disk_lsn 是用来标记当前 log buffer 中已经有那些日志被刷新到磁盘中了,该值表达的是内存中该值一开始也是8704,因此 lsn >= flushed_to_disk_lsn。log buffer 就是在磁盘中存放 redolog 的地方,log buffer 比之日志文件组就类似于 buffer pool 比之磁盘中的 Innodb

checkpoint 指的是一次刷新全局变量 checkpoint_lsn 的操作,MySQL 中可以使用 lsn 来唯一确定 redolog 位置,而 checkpoint_lsn 就指向当前可以被擦除的位置。日志文件组的大小是有限的,我们不得不循环使用日志文件组中的文件,但是如果某些日志在该日志代表的数据被刷入磁盘之前就被清理掉了,日志就没有意义了。因此可以被擦除的地方就是数据已经刷入磁盘的地方,一边擦一边往后推移,MySQL 加载日志文件组恢复数据时,会清空加载过的 redo log 记录。如果 MySQL 一直不崩溃,redolog 记录满了的话,MySQL会自动刷盘并且删除一些 redo log 记录,让 checkpoint_lsn 向后推

注意,执行一次 checkpoint 与将脏页刷新到磁盘中是不同步的,因此 checkpoint_lsn 不能代表刷新到磁盘中的数据的最新位置

redolog 恢复数据库的过程

至此,我们对数据库有了一个完整的日志记录,那如何使用这个日志记录功能呢,我们如何将数据从这个日志文件组中取出来并且用于恢复数据库呢

由于之前的全局变量与日志文件组配合记录,在 MySQL 中已经有了充足的信息。我们可以拿到需要恢复的起点(全局变量 checkpoint_lsn),和恢复的终点(可以用 lsn 来表示)。这两个值都能唯一确定一个日志文件组中的位置,并且里面的数据都保证正确性(每一条数据的末尾都有唯一标识),不会出现没有被刷入磁盘的数据对应的日志被刷掉的情况(checkpoint)

获取到日志后,我们可以一条条读取数据并且恢复,但是 MySQL 的设计者有更加快速的方法,将每个日志的 spaceID 与 page number 计算出哈希值并且存入 hash 表中,如果有多个 spaceID 与 page number 都相同的日志,将它们放入一个槽中。这样遍历槽,就可以一次性将一个页面恢复好,从而避免很多随机 IO,加快了恢复速度

这么恢复还有一点要注意,我们需要根据时间来恢复,不然最终的数据不保证正确性。同时,由于 checkpoint_lsn 与刷入磁盘的机制不一样,因此可能出现数据已经刷入了,但是日志还需要重新操作一遍的情况。重新操作一遍不会导致任何错误,但是可以优化掉这些过程,在每个页面中的文件头有个 FIL_PAGE_LSN 的属性,该属性记录了最新一次对该页面修改的日志的 lsn。我们只需要简单的判断就可以略过重新写一遍的过程了

undolog 日志

undo log 里面记录的是版本链历史记录,即之前每一行的历史版本并且使用链连接起来,所以它是一个物理日志,回滚会根据历史记录做逆向操作
在这里插入图片描述
上图主要说了数据表中的数据与 undo log 的关系

  • DB_TRX_ID 表示事务 ID,全局唯一,逐渐增大,用来唯一标识一个事务。只有在进行读写操作的时候,innodb 才会分配一个独一无二的事务 ID,事务 ID 的生成方式与分配唯一主键的方式类似,即

1,MySQL内部定义了一个全局变量,每当有事务需要id时,则将当前的值分配给该事务,并进行自增1

2,当这个变量值为256的倍数时,则刷新到系统表空间中页号为5的页面下的一个名为max trx id的属性中,该属性占用8字节的空间

3,当系统下次启动时,会将该值加载到内存中并再加上256(防止上次关闭时内存中的值没有及时刷到盘中)

  • ID 则是唯一确定表中某一条数据的 ID
  • DB_ROLL_PTR 则是回滚指针,指向了最后一条修改数据的 undo log

undolog 的类型

它记录一行行物理数据。一般对一条记录进行修改,就会记录一条 undolog,因此应该事务会记录多条 undolog,这些 undolog 会被存放在类型为 FIL_PAGE_UNDO_LOG 的页中

并且不同事务或者相同事务的对同一记录行的修改,会使该记录行的 undolog 成为一条链表,链首就是最新的记录,链尾就是最早的旧记录

不同的 undolog 有不同的组成

  • INSERT 类型的 undolog:下图中的 Col1,Col2,是针对联合主键设计的。如果记录中的主键包含多个列,则需要把每个列的占用空间大小与真实值都记录下来,len 就是占用的空间大小,而 col 则是真实值
    在这里插入图片描述
  • DELETE 类型的 undolog:我们知道数据在进行删除操作的时候会经历两步,第一步是执行语句的时候,此时将记录的 deleted_flag 标识位置为1,其他的不做处理(这步被称为 delete mark),而第二步就是在删除语句的事务提交之后,执行的真正的删除操作,将该记录从正常记录的链表中移除,并且添加到垃圾链表中,并且调整类似页头中的数据

而 undolog 的记录就是在 delete mark 过程中做的,因为需要回滚的数据只在事务执行的时候,在事务提交之后,就没必要使用 undolog 回滚了。针对以上两个前提,undolog 的设计应该可以找到更新前的链表中的位置,以及在垃圾列表中的位置

  • UPDATE 类型的 undolog:其结构如下
    在这里插入图片描述

update 的过程分为两类,一类是当数据更新后的大小与更新前的大小一致时,此时直接在用来的位置上更新数据,另一类是数据更新后的大小与更新前不一致时,此时会先删除旧记录并且添加新记录,注意,此时的删除并不是 delete mark 操作,而是将数据添加到垃圾链表的操作

这三类 undolog 日志其实有相同的部分,就是前置指针与后置指针,分别用来寻找该数据的下一次操作(或者表中的数据)与上一次操作

undolog 的页面

undo 日志存储在类型为 fil_page_undo_log 的页面中。同时,他也具有和普通的页面一样的结构,Undo Page Header 是 undo 页面独有的表头,他的部分属性如下:

  • trx_undo_page_type:本页面准备存储什么类型的 undo 日志

undo 日志类型被分为两种,第一种是 trx_undo_insert(十进制的1表示),产生的插入日志都属于这种类型,也称为 insert undo 日志,

第二种是 trx_undo_update(十进制的2表示),除了 insert 类型,其他都属于这种类型,统称 update undo 日志

undolog 的链表

一个页面只能存储一种类型的 undo 日志,不可以混合存储,之所以做出区分,是因为 insert 类型的日志可以在事务提交后直接删除,而 update 类型的由于需要为 MVCC 服务,因此要区别对待

  • trx_undo_page_start:第一条undo日志在本页面中的起始偏移量
  • trx_undo_page_free:最后一条undo日志结束时偏移量
  • trx_undo_page_node:链表节点结构

从页面的角度上来说,需要注意的点就这些了,但是从事务的角度上说,知识点还没结束。一个事务可能产生很多日志,这些日志在一个页面中可能放不下,那么就需要放到更多的页面中,这些页面就通过 trx_undo_page_node 形成了一个链表

链表中的第一个 undo 页面称为 first undo page,其余称为 normal undo page,因为第一个页面除了 undo page header 还有一些其他的管理信息,即 undo log segment header

一个事务的执行过程中,增删改的操作都会有,因为一个 undo 页面只能存放一种类型,所以一个事务的执行过程中可能有两种链表,一个是 insert undo 链表,一个是 update undo 链表

此外,Innodb 还规定,普通表和临时表的 undo 日志也要分别记录,所以一个事务中如果同时对临时表,普通表进行增删改操作,就会有4个链表

undo log segment header 拥有的部分属性如下:

1,trx_undo_state:本 undo 页面链表处于什么状态,我们可以用该属性了解事务是否结束,可能的状态有下面几种:

  • trx_undo_active:活跃状态,即一个活跃的事务正在向这个Undo页面链表中写入Undo日志。
  • trx_undo_cached:被缓存状态,该状态的Undo页面链表等待被其他事务重用。
  • trx_undo_to_free:等到被释放的状态,对于insert undo类型,在其对应的事务提交后,该链表不会被重用,就是这种状态
  • trx_undo_purge:等待被purge的状态,对于update undo类型,如果在其对应的事物提交后,该链表不能被重用,则处于这种状态

2,trx_undo_fseg_header:本 Undo 页面链表对应的段的 Segment Header 信息

3,trx_undo_page_list:Undo 页面链表的基节点,用于串联起其他页面的 trx_undo_page_node 属性,形成一个链表

因此,一个事务的 undolog 不是由页为单位来管理的,而是由链表来管理的,那应该需要一个整合的地方来管理这些链表吧,我们提出了回滚段的概念。回滚段就是被称为 Rollback Segment Header 的页面,这个页面中存放了各个 Undo 页面链表的 first undo page 的页号,这些页号被称为 undo slot。这样我们就可以提供 slot 来找到对应的链表头了

一个事务在执行过程中最多分配4个undo页面链表,一个回滚段中只有1024个undo slot,意味着同时只支持1024个事务的并发,

为了支持更多的事务执行,Innodb定义了128个回滚段,因此可以支持更多的事务,这些回滚段的页面存在系统表空间的第五个页面的一个区域中

undolog 的作用

1,MySQL 就是通过 undolog 回滚日志来保证事务原子性的,准确的说是再发生错误时提供回滚服务。在异常发生时,对已经执行的操作进行回滚,回滚日志会先于数据持久化到磁盘上(因为它记录的数据比较少,所以持久化的速度快),当用户需要回滚数据时(比如服务器断电了,再次启动数据库的时候,或者事务中出现异常需要回滚时),数据库能够通过查询回滚日志来回滚将之前未完成的事务

2,支持 MVCC(多版本并发控制),提高事务读取数据的效率

**为什么有了 redolog 还需要 undolog?**这就要从磁盘性能的优化说起

Commit Logging 有一个巨大的先天缺陷:所有对数据的真实修改都必须发生在事务提交以后,即日志写入了 Commit Record 之后。在此之前,即使磁盘有足够空闲,即使某个事务修改的数据量非常庞大,占用了大量的内存缓冲区,无论何种理由,都决不允许在事务提交之前就修改磁盘上的数据,这一点是 Commit Logging 成立的前提(因为我们我们不能使用 redolog 删除错误的记录,只能用它重做正确的记录),却对提升数据库的性能十分不利。为此,ARIES 提出了“提前写入日志”(Write-Ahead Logging)的日志改进方案,所谓“提前写入”(Write-Ahead),就是允许在事务提交之前写入变动数据的意思

Write-Ahead Logging 按照事务提交时点,将何时写入变动数据划分为 FORCE 和 STEAL 两类情况
·FORCE:当事务提交后,要求变动数据必须同时完成写入则称为 FORCE,如果不强制变动数据必须同时完成写入则称为 NO-FORCE
·STEAL:在事务提交前,允许变动数据提前写入则称为 STEAL,不允许则称为 NO-STEAL

Write-Ahead Logging 允许 NO-FORCE,也允许 STEAL,它给出的解决办法是增加了另一种被称为 Undo Log 的日志类型,当变动数据写入磁盘前,必须先记录 Undo Log,注明修改了哪个位置的数据、从什么值改成什么值等,以便在事务回滚或者崩溃恢复时根据 Undo Log 对提前写入的数据变动进行擦除

由于 Undo Log 的加入,Write-Ahead Logging 在崩溃恢复时会经历以下三个阶段

  • 分析阶段(Analysis):该阶段从最后一次检查点(Checkpoint,可理解为在这个点之前所有应该持久化的变动都已安全落盘)开始扫描日志,找出所有没有 End Record 的事务,组成待恢复的事务集合,这个集合至少会包括事务表(Transaction Table)和脏页表(Dirty Page Table)两个组成部分
  • 重做阶段(Redo):该阶段依据分析阶段中产生的待恢复的事务集合来重演历史(Repeat History),具体操作是找出所有包含 Commit Record 的日志,将这些日志修改的数据写入磁盘,写入完成后在日志中增加一条 End Record,然后移出待恢复事务集合
  • 回滚阶段(Undo):该阶段处理经过分析、重做阶段后剩余的恢复事务集合,此时剩下的都是需要回滚的事务,它们被称为 Loser,根据 Undo Log 中的信息,将已经提前写入磁盘的信息重新改写回去,以达到回滚这些 Loser 事务的目的

事务分配 undolog 的过程

事务首次修改普通表的记录时,先去系统表空间的5号页面中分配到一个回滚段,之后该事务再修改记录时,不会重复分配,多个回滚段的分配方式使用 round-robin 来分配,从第一大类中循环分配回滚段给多个事务

分配到回滚段后,查看回滚段的两个 cached 链表是否有缓存的 undo slot,不同的操作看不同的链表,insert 类的看 insert undo cached,update 类型的看 update undo cached

如果在缓存中没找到,就从回滚段中分配一个可用的 undo slot

找到可用的 undo slot,如果该 slot 是从缓存链表中获取的,其 Undo Log Segment 已经分配,否则就需要重新分配一个 Undo Log Segment,然后从该 Segment 中申请一个页面作为 Undo 页面链表的 first undo page,并把该页填入 undo slot 中

事务开始写入日志到 Undo 页面链表中

日志一致性

执行更新语句时的日志操作时,我们会同时更新这三条日志,如果两条日志的记录对不上,那么利用不同日志恢复数据之后的状态就会不一致,为此,我们需要来保证这两条日志在任何时间节点都处于一致的状态

两阶段提交

重写日志是 Innodb 提供的(这也就是 innodb 存储引擎支持事务的原因),归档日志是 mysql 提供的

binlog 主要保证 MySQL 集群架构的数据一致性,而 redolog 主要保证机器崩溃后的数据恢复工作,如果两条日志不一致会出现集群与本机的数据不一致

在 MySQL 内部,在事务提交时利用两阶段提交很好地解决了上面提到的 binlog 和 redo log 的一致性问题:

阶段1:准备阶段(Prepare Phase)

1.开始事务:MySQL客户端发起一个事务,执行一系列的 SQL 操作(如 INSERT、UPDATE、DELETE 等)
2.写入 InnoDB Redo Log:InnoDB 存储引擎将这些操作记录到其 Redo Log 中,但此时并不提交事务
3.准备提交(Prepare Commit):InnoDB 存储引擎执行 prepare 操作,将事务标记为准备提交状态。此时,InnoDB 会将事务的所有修改持久化到磁盘,并记录一个特殊的 prepare 日志记录。一旦事务进入 prepare 状态,InnoDB 保证该事务可以被提交或回滚,即使在系统崩溃后也能恢复

阶段2:提交阶段(Commit Phase)

  1. 写入 Binlog:MySQL 服务器将事务的修改操作记录到 Binlog 中并且刷盘。Binlog 记录了事务的所有 SQL 操作,以便用于主从复制和数据恢复
  2. 提交事务:MySQL 服务器通知 InnoDB 存储引擎提交事务。InnoDB 将事务从 prepare 状态转变为 commit 状态,并记录一个 commit 日志记录。此时,事务的所有修改正式生效

崩溃恢复阶段

1.在 prepare 阶段崩溃:如果在事务进入 prepare 状态后但在写入 Binlog 之前系统崩溃,恢复时会根据 Binlog 决定是提交还是回滚事务。如果 Binlog 中没有对应的记录,InnoDB 会回滚该事务
2.在 commit 阶段崩溃:如果在事务写入 Binlog 后但在 InnoDB 提交事务之前系统崩溃,恢复时 InnoDB 会根据 Binlog 中的记录是否完整来决定是否提交该事务,如果状态完整数据库是可以拿到所有的 redolog 进行重做的

因此,在这个过程中是以第二阶段中 binlog 的写入与否作为事务是否成功提交的标志

2PC 提交

MySQL 的两阶段提交机制借鉴了经典的 2PC 协议的思想,但它的应用场景和具体实现有所不同。经典的 2PC 协议用于分布式系统中的事务协调,而 MySQL 的两阶段提交主要用于协调 InnoDB 存储引擎和 Binlog 的写入。以下是经典的 2PC 提交流程,两者对比,发现具体的算法流程还是有很大不同的,不能将他们混为一谈

  • 准备阶段:事务发起者首先向协调者发起事务请求,然后协调者会给所有参与者发送 prepare 请求(其中包括事务内容),参与者收到 prepare 消息后,他们会开始执行事务(但不提交),之后参与者就向协调者反馈是否准备好了

  • 提交阶段:如果所有的参与者都返回了准备好了的消息,这个时候就进行事务的提交,协调者此时会给所有的参与者发送 Commit 请求,当参与者收到 Commit 请求的时候会执行前面执行的事务的提交操作,提交完毕之后将给协调者发送提交成功的响应;如果并不是所有参与者都返回了准备好了的消息,那么此时协调者将会给所有参与者发送回滚事务的 rollback 请求。当然这个回滚的操作相对重负载了,毕竟需要回滚之前已提交的事务

在 mysql 集群中,事实上是使用异步复制、半同步或者全同步来保证集群数据一致性的。这三者的时机都发生在二阶段提交的提交阶段

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值