【MySQL高阶】21.撤销表空间,撤销日志

文章目录

  • [6. InnoDB 磁盘文件](#6. InnoDB 磁盘文件)
    • [6.6 撤销表空间 - Undo Tablespaces](#6.6 撤销表空间 - Undo Tablespaces)
      • [6.6.4 如何删除撤销表空间?](#6.6.4 如何删除撤销表空间?)
        • [6.6.4.1 删除撤销表空间的示例](#6.6.4.1 删除撤销表空间的示例)
        • [6.6.4.2 撤销表空间被置为不活动并且已被截断为初始大小,这时不想删除了是否可以重新启用?](#6.6.4.2 撤销表空间被置为不活动并且已被截断为初始大小,这时不想删除了是否可以重新启用?)
      • [6.6.5 如何查看撤销表空间的状态?](#6.6.5 如何查看撤销表空间的状态?)
    • [6.7 撤销日志 - Undo Log](#6.7 撤销日志 - Undo Log)
      • [6.7.1 什么是撤销日志?](#6.7.1 什么是撤销日志?)
        • [6.7.1.1 撤销日志的写入时机?](#6.7.1.1 撤销日志的写入时机?)
      • [6.7.2 撤销日志在撤销表空间中的组织形式是怎样的?](#6.7.2 撤销日志在撤销表空间中的组织形式是怎样的?)
      • [6.7.3 撤销日志的格式是怎样的?](#6.7.3 撤销日志的格式是怎样的?)
        • [6.7.3.1 在事务中不同的DML操作对应的撤销日志是否不同?](#6.7.3.1 在事务中不同的DML操作对应的撤销日志是否不同?)
        • [6.7.3.2 不同操作对应的撤销日志如何区分?](#6.7.3.2 不同操作对应的撤销日志如何区分?)
      • [6.7.4 撤销日志是如何组织在一起的?](#6.7.4 撤销日志是如何组织在一起的?)
        • [6.7.4.1 如何理解Undo链以及它是如何构成的?](#6.7.4.1 如何理解Undo链以及它是如何构成的?)
        • [6.7.4.2 事务提交后Undo Log是否就可以删除了?](#6.7.4.2 事务提交后Undo Log是否就可以删除了?)
      • [6.7.5 撤销日志如何分类?](#6.7.5 撤销日志如何分类?)
      • [6.7.6 InnoDB最大支持并发读写事务的数量如何计算?](#6.7.6 InnoDB最大支持并发读写事务的数量如何计算?)
      • [6.7.7 如何理解Undo链?](#6.7.7 如何理解Undo链?)
      • [6.7.8 撤销日志为什么需要落盘?](#6.7.8 撤销日志为什么需要落盘?)
        • [6.7.8.1 撤销日志在内存中如何记录?](#6.7.8.1 撤销日志在内存中如何记录?)
        • [6.7.8.2 撤销日志的写入过程是怎样的?](#6.7.8.2 撤销日志的写入过程是怎样的?)
        • [6.7.8.3 撤销日志的回滚过程是怎样的?](#6.7.8.3 撤销日志的回滚过程是怎样的?)
        • [6.7.8.4 撤销日志的清理过程是怎样的?](#6.7.8.4 撤销日志的清理过程是怎样的?)

6. InnoDB 磁盘文件

6.6 撤销表空间 - Undo Tablespaces

6.6.4 如何删除撤销表空间?

MySQL 8.0.14开始使用 CREATE UNDO TABLESPACE 语法创建的撤销表空间可以使用 DROP UNDO TABALESPAC 语法删除;

撤销表空间在被删除之前必须是空的,要清空撤销表空间,必须首先使用 ALTER UNDO TABLESPACE 语法将撤销表空间标记为不活动,以便该表空间不再用于其他新的事务(也就是没有新的日志往里面写);

mysql 复制代码
# 语法
ALTER UNDO TABLESPACE tablespace_name SET INACTIVE;

在将undo表空间标记为非活动后,等待当前undo表空间的事务完成后表空间被截断到初始大小,当undo表空间为空,就可以进行删除操作

mysql 复制代码
# 语法 
DROP UNDO TABLESPACE tablespace_name;

总结:

  • MySQL 8.0.14开始使用 CREATE UNDO TABLESPACE 语法创建的撤销表空间可以使用 DROP UNDO TABALESPAC 语法删除,但要确保撤销表空间在被删除之前必须是空的,具体的操作步骤如下:

    • 要删除的撤销表空间必须是通过create语句创建的

    • 将撤销表空间标记为不活动状态

    • 等待当前undo表空间日志对应的事务完成后,也就是日志没用后,等待撤销表空间中的日志被清空

    • 通过drop语句。执行删除操作


6.6.4.1 删除撤销表空间的示例
mysql 复制代码
# 查询指定的表空间状态
mysql> SELECT NAME, STATE FROM INFORMATION_SCHEMA.INNODB_TABLESPACES
    ->  WHERE NAME LIKE 'tablespace_test';
+-----------------+--------+
| NAME            | STATE  |
+-----------------+--------+
| tablespace_test | active | # 活动状态
+-----------------+--------+
1 row in set (0.00 sec)

# 设置为不活动状态
mysql> ALTER UNDO TABLESPACE tablespace_test SET INACTIVE;
Query OK, 0 rows affected (0.00 sec)

# 再次查询状态
mysql> SELECT NAME, STATE FROM INFORMATION_SCHEMA.INNODB_TABLESPACES  WHERE NAME LIKE 'tablespace_test';
+-----------------+-------+
| NAME            | STATE |
+-----------------+-------+
| tablespace_test | empty | # 表空间为empty(已经情况),也有可能是inactive (等待清空)
+-----------------+-------+
1 row in set (0.00 sec)

# 当表空间状态为empty时,要以进行删除操作
mysql> DROP UNDO TABLESPACE tablespace_test;
Query OK, 0 rows affected (0.01 sec)

# 查询撤销表空间,发现删除成功
mysql> SELECT TABLESPACE_NAME, FILE_NAME FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE LIKE 'UNDO LOG';
+-----------------+------------+
| TABLESPACE_NAME | FILE_NAME  |
+-----------------+------------+
| innodb_undo_001 | ./undo_001 |
| innodb_undo_002 | ./undo_002 |
+-----------------+------------+
2 rows in set (0.00 sec)

mysql> 

6.6.4.2 撤销表空间被置为不活动并且已被截断为初始大小,这时不想删除了是否可以重新启用?

undo表空间状态为空时,可以重新激活,方法如下:

mysql 复制代码
# 语法
ALTER undo tablespace tablespace_name SET ACTIVE;

6.6.5 如何查看撤销表空间的状态?

通过 SHOW STATUS LIKE 'Innodb_undo_tablespaces%'; 语句可以查看撤销表空间的基本信息

mysql 复制代码
mysql> SHOW STATUS LIKE 'Innodb_undo_tablespaces%';
+----------------------------------+-------+
| Variable_name                    | Value |
+----------------------------------+-------+
| Innodb_undo_tablespaces_total    | 2     | # 撤销表空间的总数
| Innodb_undo_tablespaces_implicit | 2     | # 隐式(InnoDB)创建撤销表空间数量
| Innodb_undo_tablespaces_explicit | 0     | # 显式(用户)创建撤销表空间数量
| Innodb_undo_tablespaces_active   | 2     | # 活动中的撤销表空间数量
+----------------------------------+-------+
4 rows in set (0.00 sec)

mysql> 

6.7 撤销日志 - Undo Log

6.7.1 什么是撤销日志?

  • 当事务对数据进行修改的时候,每个修改操作都会在磁盘上创建与之对应的Undo Log,当事务需要回滚时,会根据Undo Log逐一进行撤销操作,从而保证事务的原子性。也就是说撤销日志是为事务的回滚操作而诞生的机制,它是一个撤销操作记录的集合。
  • Undo日志保存在Undo日志段中,Undo日志段位于回滚段中,回滚段位于undo表空间(撤销表空间)和全局临时表空间中。
    官网描述:

回滚日志是与单个读写事务相关联的一组回滚日志记录。回滚日志记录包含有关如何撤销事务对聚簇索引记录的最新更改的信息。如果另一个事务需要在一致性读取操作中查看原始数据,则会从回滚日志记录中检索未修改的数据。回滚日志存在于回滚日志段中,回滚日志段包含在回滚段中。回滚段位于回滚表空间和全局临时表空间中。


6.7.1.1 撤销日志的写入时机?

在事务执行每个DML之前,会根据DML构建对应的撤销日志,并申请一个 undo log segments (撤销日志段),把日志记录在申请到的撤销段中,再执行真正的DML操作,执行过程如下所示:


6.7.2 撤销日志在撤销表空间中的组织形式是怎样的?

首先看来撤销日志在撤销表空间中的组织结构图,如下所示:

  • Undo log segments (撤销日志段)也称为撤销段,一个撤销日志段可以保存多个事务的回滚日志,但在同一时间只能被一个活跃事务使用,对应的空间在事务提交或回滚后才可以被重用。
  • rollback segments (回滚段)中包含撤销日志段,通常位于undo表空间和全局临时表空间中,使用系统变量 Innodb_rollback_segments 可以定义分配给每个undo表空间和全局临时表空间的回滚段的数量,默认值为128,取值范围是[1, 128]
  • 一个回滚段支持的事务数取决于回滚段中的undo slots(槽数)和每个事务所需的undo日志数,一个回滚段中的undo槽数可以根据InnoDB页面大小进行计算,公式是(InnoDB Page Size / 16),比如默认情况下 InnoDB Page Size 大小为 16KB ,那么一个回滚段就可以包含 1024undo slot 用来存储事务的撤销日志。
  • 回滚段中还记录了 History List 的头节点 History List Base Node ,以及回滚段的大小
  • 通过系统变量 innodb_rollback_segments 可以设置Undo表空间中的回滚段数量,最大值和默认值都是128
mysql 复制代码
# 查看Undo表空间中的回滚段数量
mysql> show variables like 'innodb_rollback_segments';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_rollback_segments | 128   |
+--------------------------+-------+
1 row in set (0.00 sec)

# 设置Undo表空间中的回滚段数量
mysql> SET GLOBAL innodb_rollback_segments = 128;
Query OK, 0 rows affected (0.00 sec)

mysql> 

总结:

撤销表空间中包含 rollback segments (回滚段),每个回滚段中包含若干undo slots(槽数),每个槽对应一个 Undo log segments (撤销日志段),撤销日志段中包含具体的撤销日志。


6.7.3 撤销日志的格式是怎样的?

撤销日志格式示意图如下:

一条记录在Undo Log页中的Undo Log日志大体包含两部分:分别是记录了Undo类型、表ID、上一条、下一条日志的偏移地址等在内的"基本信息",以及记录了不同操作和数据的"操作信息",如上图所示


6.7.3.1 在事务中不同的DML操作对应的撤销日志是否不同?

在执行DML语句操作数据库时,不同SQL语句对应的撤销操作不同,不同的撤销操作对应的Undo Log(撤销日志)存储格式也不相同。

按照增、删、改等不同的DML操作,生成对应的撤销日志。


6.7.3.2 不同操作对应的撤销日志如何区分?
  • Undo类型有很多种,最常见的就是增、删、改,分别用 TRX_UNDO_INSERT_REC 、TRX_UNDO_DEL_MARK_RECTRX_UNDO_UPD_EXIST_REC 表示,如图所示:
  • 新增( TRX_UNDO_INSERT_REC )时的Undo Log操作信息相对简单,只记录了主键值,主键长度等主键信息;

  • 删除( TRX_UNDO_DEL_MARK_REC )时除了记录主键信息之外,还记录了旧的事务ID,旧的ROLL_POINTER(回滚指针)信息,用来构建有序的Undo版本链,还会记录一些被索引字段的信息;

  • 更新( TRX_UNDO_UPD_EXIST_REC )时较为复杂:

    如果不更新主键则和删除时类似,会记录主键信息、旧的事务ID、旧的ROLL_POINTER信息、被索引字段的信息和被更新的信息;

    如果更新了主键,则会记录两条Undo Log ,一条删除的和一条新增的;(对于更新主键的操作,本质上都是删除旧数据,添加新数据)


6.7.4 撤销日志是如何组织在一起的?

撤销日志的组织示意图如下

  • 一条条Undo Log会被逐一放在Undo Log页中,Undo Log页和其他类型的页一样都会包含头尾信息,除此之外还有Undo Log特有的信息,包括:
    • UNDO PAGE HEADER
      1. 记录了Undo Log页类型
      2. 第一条日志偏移位置
      3. 上一页下一页链表引用等信息
    • UNDO LOG SEGMENT HEADER :记录了回滚段信息:
      1. 标记当前这个段是不活跃的
      2. 记录最后一个事务的偏移量
      3. 记录段中第一个页的位置
    • UNDO LOG HEADER :每个事务都是以UNDO LOG HEADER开始作为事务开始的标记
      1. 记录了产生这条日志的事务Id:Trx ID;记录事务的编号,记录事务第一条日志的偏移量
      2. 事务的提交顺序:Trx No
      3. 其他事务相关信息
  • 在这三个特有的头信息之外,其他空间都会用来记录Undo Log日志,如果某个事务很大,一个Undo Log页没有办法完整记录,就需要申请新的Undo Log页,然后通过 UNDO PAGE HEADER中链表引用信息链接到前一个页,后面的这些页只需要记录Undo Log并不需要记录Undo头信息。
  • 这个由Undo Log构成的链表称做Undo链,在事务中会起到非常重要的作用。

6.7.4.1 如何理解Undo链以及它是如何构成的?

本小节最后会详细介绍Undo链的构成


6.7.4.2 事务提交后Undo Log是否就可以删除了?
  • 这里强调一下,对于新增操作所记录的Undo Log日志,在事务提交之后就可以直接删除了,而删除和更新的Undo Log日志还需要服务事务的MVCC,所以并不能直接删除,而是加入到hisotry list 中。(主要是根据当前修改的数据有没有上一个版本而定)

    因些InnoDB为了最大程度节省空间提升效率对Undo Log进行了分类

  • 关于 MVCChisotry list 在事务和锁专题中详细介绍

所有的事务都不访问的数据才会删除


6.7.5 撤销日志如何分类?

  • Undo Log分为两大类:

    1. 一类只记录新增操作,事务提交后可以直接清除;
    2. 另一类记录删除和更新操作,所以相应的回滚链也会被区分为2个:Insert Undo链 和 Update Undo链(Delete + Update)

    如是是INSERT 操作,这个页中保存UNDOLOG 全都是关于INSERT操作的UNDO 日志。

    如是是UPDATEDELETE 操作,这个页中保存UNDO LOG全都是关于UPDATEDELETE操作的UNDO 日志。

    也就是说一个事务可能会申请不同类型的页。

  • 另外普通表和临时表分别对应这两类Undo链,如是一个事务既有新增又有修改并且用到了临时表,那么这个事务最多可以分配四个撤销日志,也就是四个Undo链,分别是:

    1. 对用户定义的普通表进行 INSERT 操作(保存在系统或用户创建的撤销表空间中)
    2. 对用户定义的普通表进行 UPDATEDELETE 操作(保存在系统或用户创建的撤销表空间中)
    3. 对用户定义的临时表进行 INSERT 操作(保存在临时表空间中,临时表空间中有一个回滚段专用于保存UNDO日志)
    4. 对用户定义的临时表进行 UPDATEDELETE 操作(保存在临时表空间中,临时表空间中有一个回滚段专用于保存UNDO日志)
  • 根据事务的操作按需要写入Undo日志,比如,在普通表和临时表执行 INSERTUPDATEDELETE 操作的事务需要会写入以上四种类型的撤销日志。

    只在普通表上执行 INSERT 操作的事务只需要一个撤消日志。

  • 对普通表执行操作的事务将从指定的系统表空间或undo表空间的回滚段分配undo日志。

    对临时表执行操作的事务从指定的临时表空间回滚段分配undo日志。


6.7.6 InnoDB最大支持并发读写事务的数量如何计算?

  • 可以用以下公式计算InnoDB能够支持的并发读写事务的数量
  • 如果每个事务执行 INSERTUPDATEDELETE 操作,注意只执行一种类型的操作,并发读写事务数为:
    名词解释:

innodb_page_size:页大小

innodb_rollback_segments:回滚段的数量

number of undo tablespaces:撤销表空间的个数

mysql 复制代码
# 最大支持 (16384 / 16) * 128 * 127 = 16,646,144
(innodb_page_size / 16) * innodb_rollback_segments * number of undo tablespaces

如果每个事务执行 INSERTUPDATEDELETE 操作,并发读写事务数为:

mysql 复制代码
# 最大支持 (16384 / 16 / 2) * 128 * 127 = 8,323,072
(innodb_page_size / 16 / 2) * innodb_rollback_segments * number of undo tablespaces

如果一个事务在临时表上执行 INSERT 操作,并发读写事务数为:

mysql 复制代码
# 临时表最大支持 (16384 / 16) * 128 = 131,072
(innodb_page_size / 16) * innodb_rollback_segments

如果一个事务在临时表上执行 INSERTUPDATEDELETE 操作,并发读写事务数为:

mysql 复制代码
# 临时表最大支持 (16384 / 16 / 2) * 128 = 65,536
(innodb_page_size / 16 / 2) * innodb_rollback_segments

6.7.7 如何理解Undo链?

  • Insert Undo链 和 Update Undo链采用了不同的组织方式;
  • 对于新增操作,Insert Undo链中的每个Undo Log都会对应一条新的数据行,这个数据行中用ROLL_POINTER 信息来关联Undo Log,在回滚时就可以通过它找到需要回滚的Undo Log了,如图所示:
  • 对于删除和更新,Update Undo链中的每个Undo Log也都对应一个数据行,每次更新都会通过Undo Log中的 ROLL_POINTER 进行关联,从而每个数据行都会构成一个Undo Log版本链,回滚的时候就可以依序撤销,这个版本链在事务的MVCC中起到了非常重要的作用,用于解决事务的"隔离性",相关内容在事务和锁专题中详细介绍。

    1. 一条Update Undo链包含多条Undo日志
    2. Undo日志的条数与对数据行进行的Update次数相关
    3. 每个被修改的数据行都对应着自己的Update Undo

    Update Undo链如下图所示:

  • 以下是一个关于更新操作的Undo

6.7.8 撤销日志为什么需要落盘?

  • 在对数据进行修改时,都是在内存中操作的,也就是在Buffer Pool中修改对应的数据页,在修改数据页之前先把对应的撤销日志记录在内存中,如果此时事务回滚直接根据内存中的撤销日志做回滚操作即可;

  • 在修改完成提交事务后,脏页进行落盘操作,此时事务已提交,不能回滚,所以撤销日志也就失效了;

  • 当服务器崩溃时,如果事务没有提交,所有的修改都在内存中,还没有落盘,对于修改直接丢弃(因为是不完整的事务);如果事务已经提交,则根据重做日志和双写缓冲区中的备份进行恢复;

  • 通过分析看上去撤销日志并不需要落盘,其实以上的分析场景并没有考虑到全部的场景,比如:大事务的运行(一个事务需要运行很久)、MVCC中版本链什么时候可以销毁、事务的不同隔离级别等因素;
    总结:

  • 在运行大事务时,InnoDB为了避免大事务提交时统一落盘操作带来的性能问题,允许在事务进行的过程中就进行落盘操作并记录对应的UndoLog,当发生崩溃恢复时,通过回放UndoLog把未提交的事务进行回滚;(Undo Log落盘必须在真实数据页落盘之前)

  • 如果一个事务已经提交,但还有其他事务需要访问版本链中对应的Undo Log,那么也需要把相应的撤销日志保存到 hisotry list 中。

  • 不同隔离级别下,没有提交的事务也可能会落盘,回滚时依然要完成撤销操作


6.7.8.1 撤销日志在内存中如何记录?
  • 与数据页在内存中的保存方式相同,撤销日志在内存中也保存在Buffer Pool中,与磁盘中的Undo Log页结构一致,内存中每个Undo Log页通过控制块管理
  • 在内存中使用四个链表来管理正在使用的UndoLog页和空闲UndoLog页,根据不同的日志类型分为:
    1. Insert List :正在被使用的用来管理Insert类型的UndoLog
    2. Insert Cache List :空闲的或被清理后可以被后续事务重用的Insert类型UndoLog
    3. Update List :正在被使用的用来管理Update类型的UndoLog
    4. Update Cache List :空闲的或被清理后可以被后续事务重用的Update类型UndoLog

6.7.8.2 撤销日志的写入过程是怎样的?
  • 当写事务开始时,会先分配一个处理 ACTIVE 状态的 Rollback Segment (回滚段);

  • 当第一次DML操作产生Undo Record时,会轮询当前 Rollback Segment 中可用的 Slot(槽) ,以便获取一个 Undo Log Segment(撤销日志段) ;

  • 根据撤销日志的类型获取UndoLog页,并挂载到对应的List当中;

  • UndoLog页顺序写入日志,当一个UndoLog页写满之后,会获取新的UndoLog页以便继续写入当前事务生成的日志。

    这里注意:单条UndoLog不能跨页存储,也就是说当某条日志在当前页中放不下时,会整体保存下一页中;

  • 由后台线程把日志刷入磁盘;

  • 当事务结束之后(commit或者rollback), insert 日志类型对应的 Undo Log SegmentUndoLog page 会直接回收,而 update 日志类型对应的 Undo Log Segment 和UndoLog page 会等待后台的清理操作完成后,确保日志不会有事务再访问后进行回收,回收的UndoLog页被挂载到Cache List(缓存链表)中。


6.7.8.3 撤销日志的回滚过程是怎样的?
  • 回滚操作可以是用户通过rollback主动触发,也可能发生在崩溃恢复时,不论是哪种触发条件,回滚操作都是相同的,基本过程就是读取该事务的Undo Log,从后向前依次进行逆向操作,从而恢复索引记录;
  • 对于 Insert 类型的回滚操作就是 Delete ,在删除的过程中会重新调整主键索引和二级索引;
  • 对于UpdateDelete类型的回滚操作,主要是回退这次操作在所有主键索引和二级索引的影响,可能包括重新插入被删除的二级索引记录、去除行管理信息中的Delete Mark标记、将主索引记录修改回之前的值等;
  • 完成回滚的Undo Log会进行回收,将不再使用的UndoLog页的磁盘空间还给 Undo Log Segment ,这个过程是写入过程的逆操作。

6.7.8.4 撤销日志的清理过程是怎样的?
  • InnoDB通过保存多份Undo Log的历史版本来实现MVCC,当某个历史版本已经确认不会被任何现有的和未来的事务访问时,就应该被清理掉;
  • 当开启一个事务时都会被分配一个事务编号 trx_id ,而事务进行读操作时会创建一个ReadView(读视图/读快照),并记录当前所有事务中的最小活跃事务编号 m_low_limit_id ,如果版本链中日志的trx_id < m_low_limit_id ,则表示当前读操作发生时,日志对应的事务已提交,其修改的新版本是可见的, 因此不再需要通过Undo版本链构建之前的版本,这个事务的Undo Log也就可以被清理了。
  • Undo的清理工作是由专门的后台线程进行扫描和分发,并由多个线程进行清理,并可以通过系统变量 innodb_purge_threads 配置清理线程数,系统变量 innodb_purge_batch_size可以配置每次清理的页数,这里不再进行过多的讨论。
相关推荐
We Just Keep growing1 小时前
【MySQL运维篇】——日志、主从复制、分库分表、读写分离
java·运维·数据库·windows·学习·mysql
每天都要进步哦1 小时前
MySQL快速入门指南:从零基础到基本操作
数据库·mysql·oracle
bjzhang752 小时前
mysql 常用命令
数据库·mysql
我命由我123452 小时前
Android 开发,FragmentPagerAdapter 的 isViewFromObject 方法问题
android·java-ee·kotlin·android studio·android jetpack·android-studio·android runtime
weiggle2 小时前
第五篇:Modifier 解析——链式调用的艺术
android
awu的Android笔记2 小时前
Android 弱网模拟:别只会用均匀分布——三种延迟模型和两种丢包模型的原理与实现
android·tcp/ip
Database_Cool_3 小时前
AnalyticDB MySQL vs Hologres:阿里云内部数仓产品如何选——场景化选型指南
数据库·数据仓库·mysql·阿里云
todoitbo3 小时前
从 mysql 命令切到 ksql,第一步先把连接搞明白
数据库·mysql·国产数据库·ksql