Oracle、MySQL等关系型数据库最重要的技术之一就是UNDO,它保障了事务的原子性,同时提供了MVCC特性,让数据库能解决高并发的问题,尽管不同数据库,对UNDO的实现略不同,但整体上的逻辑,都是相通的。

技术社群的这篇文章《技术分享 | MySQL Undo 工作机制历史演变(文末活动)》,杨老师给我们讲解了MySQL中UNDO相关的基础知识,可以了解学习,将其用到实际工作中,作到学以致用。

和UNDO相关的历史文章如下,

《Oracle UNDO表空间占满的解决场景》

《MySQL日志 - Undo和Redo日志》

《MySQL日志 - Undo回滚日志的介绍》

《了解一些MySQL的Undo Log基础知识》

《delete操作对UNDO表空间容量的冲击》

1前言

前几天遇到一个关于 Undo 变大的 CASE(具体场景参见文章末尾),为了方便后续排查问题,于是系统的梳理 Undo 表空间的相关知识,希望对读者朋友有所帮助。

2Undo 工作机制

保障事务原子性,提供数据回滚

当数据库崩溃时或者事务回滚时,InnoDB 事务系统可以利用 Undo Log 来进行数据回滚。

多版本并发控制(MVCC)- 隔离性

InnoDB 存储引擎中 MVCC 的实现是通过 Undo Log 来实现的。

当事务 A 读取某一行记录时,若该记录已经被其他事务 B 占用,当前事务 A 可以通过 Undo Log 读取之前的行版本信息,以此实现非锁定读取(如果有长时间的查询,会导致历史的 Undo 不能及时清理,进而导致 Undo Log 膨胀)。

3UNDO 表空间管理发展史

3.1 MySQL 5.6 版本之前

Undo Tablespace 是在 ibdata 中与系统表空间一起。比较常见的问题是由于大事务不提交导致 ibdata 膨胀,而且事务提交之后不能回收空间,进而浪费大量的空间甚至把磁盘打爆,同时也增加了数据库物理备份的时间。

此时,重建数据库是唯一的解决方法。

3.2 MySQL 5.6 版本

InnoDB 支持设置独立的 Undo Tablespace,也即 Undo Log 可以存储于 ibdata 文件之外。但是该特性存在一定的限制,

  1. 使用者必须在 初始化实例的时候,通过设置 innodb_undo_tablespaces 的值来实现 Undo Tablespace 独立,而且在初始化完成后不可更改。默认值为 0,表示不独立设置 Undo 的 Tablespace,默认记录到 ibdata 中。
  2. 修改  innodb_undo_tablespaces 的值会导致数据库无法启动。
  3. Undo Tablepsace 的 Space ID 必须从 1 开始,无法增加或者删除 Undo Tablespace。

3.3 MySQL 5.7 版本

引入一个 让 DBA 开心的功能 -- 在线 Truncate Undo Tablespace。该功能通过  innodb_undo_log_truncate 参数来控制。

3.4 MySQL 8.0 版本

MySQL 对 Undo Tablespace 进一步优化。

  1. 在 8.0 版本中,独立 Undo Tablespace 特性默认打开。从 8.0.3 版本开始,默认 Undo Tablespace 的个数从 0 调整为 2。
  2. 支持动态在线增加/删除 Undo Tablespace 。
  3. Undo Tablespace 的命名从 undoNNN 修改为 undo_NNN 。
  4. 在 8.0 之前只能创建 128个 回滚段,而在 8.0 版本开始,每个 Undo Tablespace 可以创建 128 个回滚段。共有 innodb_rollback_segments * innodb_undo_tablespaces 个回滚段。在高并发下可以显著的减少因为分配到同一个回滚段内的事务间产生的锁冲突,从而提高系统并行性能。
  5. Innodb_undo_truncate 参数默认打开, Undo Tablespace 大小超过 innodb_max_undo_log_size 来控制时,就会触发 Online Truncate。
  6. 支持 Undo Tablespace 加密。

4Undo 表空间维护

我们通过模拟 Undo 文件增大、手动添加 Undo 表空间和文件的过程,来学习 Undo 表空间的管理操作。基本思路是,

  1. 一般默认 MySQL 实例有 2 个 Undo Tablespace。
  2. 添加新的 Undo 表空间 A。
  3. 设置老的 Undo 表空间 B 为 inactive,系统基于 innodb_undo_log_truncate =ON 自动回收 Undo 文件空间。
  4. 设置老的 Undo 表空间 B 为 active。
  5. 此时可以保留新的 Undo 表空间 A 或者设置 Undo 表空间 A 为 inactive ,然后删除。

接下来我们根据以上的思路进行测试。

1. 查看当前 Undo 表空间和状态
mysql > SELECT NAME, STATE 
        > FROM INFORMATION_SCHEMA.INNODB_TABLESPACES 
        > WHERE NAME LIKE '%undo%';
+-----------------+--------+
| NAME            | STATE  |
+-----------------+--------+
| innodb_undo_001 | active |
| innodb_undo_002 | active |
+-----------------+--------+
2 rows in set (0.02 sec)
2. 添加新的 Undo 表空间

系统默认会分配 2 个 innodb 开头的表空间。如果手工新增 Undo 表空间,创建 innodb 开头的表空间名称会报错,提示以 innodb 开头的表空间名称被系统占用。

mysql > create undo tablespace innodb_undo_003 add datafile 'undo_003.ibu';
ERROR 3119 (42000): InnoDB: Tablespace names starting with `innodb_` are reserved.

文件必须是 .ibu 结尾,否则也是会报错。

3. 创建 Undo Tablespace

创建 Undo Tablespace 文件 undo_003

mysql > create undo tablespace undo_003 add datafile 'undo_003.ibu';
Query OK, 0 rows affected (0.14 sec)
mysql > SELECT NAME, STATE 
        > FROM INFORMATION_SCHEMA.INNODB_TABLESPACES 
        > WHERE NAME LIKE '%undo%';
+-----------------+--------+
| NAME            | STATE  |
+-----------------+--------+
| innodb_undo_001 | active |
| innodb_undo_002 | active |
| undo_003        | active |
+-----------------+--------+
3 rows in set (0.00 sec)
4. 自动回收 Undo 文件空间

设置 innodb_undo_002 为 inactive ,让系统自动收缩 Undo 文件。

mysql > ALTER UNDO TABLESPACE innodb_undo_002 SET INACTIVE;
Query OK, 0 rows affected (0.00 sec)
mysql > SELECT NAME, STATE FROM INFORMATION_SCHEMA.INNODB_TABLESPACES WHERE NAME LIKE '%undo%';
+-----------------+--------+
| NAME            | STATE  |
+-----------------+--------+
| innodb_undo_001 | active |
| innodb_undo_002 | empty  | ## 状态为 empty 时即可触发系统 undo表空间回收
| undo_003        | active |
+-----------------+--------+
3 rows in set (0.00 sec)

我们不能删除系统默认创建 innodb 开头的 Undo 表空间,系统会提示 该空间为系统保留空间

mysql > DROP UNDO TABLESPACE  innodb_undo_002;
ERROR 3119 (42000): InnoDB: Tablespace names starting with `innodb_` are reserved.
mysql >ALTER UNDO TABLESPACE innodb_undo_002 SET ACTIVE;
Query OK, 0 rows affected (0.00 sec)
5. 删除新的 Undo 表空间
mysql > ALTER UNDO TABLESPACE undo_003 SET INACTIVE;
Query OK, 0 rows affected (0.00 sec)
mysql > SELECT NAME, STATE FROM INFORMATION_SCHEMA.INNODB_TABLESPACES WHERE NAME LIKE '%undo%';
+-----------------+--------+
| NAME            | STATE  |
+-----------------+--------+
| innodb_undo_001 | active |
| innodb_undo_002 | active |
| undo_003        | empty  |
+-----------------+--------+
3 rows in set (0.00 sec)
mysql >DROP UNDO TABLESPACE undo_003;
Query OK, 0 rows affected (0.01 sec)
mysql > SELECT NAME, STATE FROM INFORMATION_SCHEMA.INNODB_TABLESPACES WHERE NAME LIKE '%undo%';
+-----------------+--------+
| NAME            | STATE  |
+-----------------+--------+
| innodb_undo_001 | active |
| innodb_undo_002 | active |
+-----------------+--------+
2 rows in set (0.00 sec)

5UNDO 相关参数

  1. innodb_undo_directory:存储 Undo 文件的目录。
  2. innodb_undo_log_truncate:用于打开/关闭 Truncate Undo 特性,可动态调整。MySQL  8.0 默认开启。
  3. innodb_undo_tablespaces:默认为 2,用于初始化实例时设置 Undo Tablespace 的个数,该参数可以动态调整。要实现在线 Truncate Undo,该参数需要大于等于 2,因为在 Truncate 一个 Undo Log 文件时,需要保证另外一个是可用的。手动维护的时候可以设置为 3 或者更大。
  4. innodb_purge_rseg_truncate_frequency:默认最大值 128,用于控制 purge 回滚段的频率。也就是 128 次后才会触发一次 Undo 的 Truncate,而每次清理的 Undo Page 由 innodb_purge_batch_size 参数决定。innodb_purge_batch_size 默认为 300,也即 300×128 个 Undo 批次清理后才会触发 Undo 表空间的收缩操作。该参数越小,Undo 表空间被尝试 Truncate 的频率越高。
  5. innodb_max_undo_log_size:控制 Undo 表空间文件的大小,超过这个阈值时才会去尝试 Truncate。Truncate 后的大小默认为 10M。

如果您认为这篇文章有些帮助,还请不吝点下文章末尾的"点赞"