MySQL - undo log 图文详解

一 前言

undo log 是 innodb 实现,总的来说提供两个作用:回滚和多版本控制(MVCC)。是事务特性的重要组成部分,在数据发生更新操作时候(INSERT、DELETE、UPDATE)时会产生 undo 记录。 先于 redo log 被记录

  1. 事务回滚:这里的回滚不仅代表程序进行的主动回滚,也包括比如事务进行中数据库宕机后恢复时对部分不满足继续提交事务条件的事务进行回滚。
  2. MVCC:提供快照读条件,以实现非锁定一致性读。如一个事务读被其他事务占用的记录时候,可以通过 undo log 链拿到之前的数据。

与 redo log 不同的是 undo log 是逻辑日志,可以勉强理解为 redo log 的相反操作。例如 redo log 的 insert 对应着 undo log delete 操作。

二 相关参数

不同于之前章节将参数部分放到小节最后,本节需要先了解部分相关参数后会对后面的内容有一定的理解帮助。

与之相关的参数大致如下所示(5.6 之前版本有些差异,不在本文讨论范围了):

mysql> show variables like '%undo%';
+--------------------------+----------+
| Variable_name            | Value    |
+--------------------------+----------+
| innodb_max_undo_log_size | 10485760 |
| innodb_undo_directory    | ./       |
| innodb_undo_log_truncate | OFF      |
| innodb_undo_logs         | 128      |
| innodb_undo_tablespaces  | 0        |
+--------------------------+----------+

mysql> show global variables like '%truncate%';
+--------------------------------------+-------+
| Variable_name                        | Value |
+--------------------------------------+-------+
| innodb_purge_rseg_truncate_frequency | 128   |
+--------------------------------------+-------+

参数解答:

  1. innodb_undo_tablespaces:是否启用独立 undo log 表空间。
    = 0: 开启,此时 undo log 存放于数据文件 ibdata 中。
    >= 1:不开启,此时 undo log 存放于 innodb_undo_directory 目录下如:undo001、undo002 的独立文件中。其大小由参数 innodb_max_undo_log_size 控制,truncate 默认 10MB。
    注意:该参数必须在实例初始化时候指定,之后不能更改。
    何时启用:比如实例写压力大,此时可以考虑和数据文件分离出来。
  2. innodb_max_undo_log_size:设置单个独立 undo log 表空间大小。
  3. innodb_undo_directory:设置独立 undo log 表空间文件目录。
  4. innodb_undo_logs:自定义 rollback segment(段)数量。
  5. innodb_undo_log_truncate:开关状态控制 purge 线程进行空间回收和 undo file 的重新初始化,该线程触发会受 innodb_max_undo_log_size 、truncate 频率等影响。
    必要条件:已设置独立表空间且独立表空间个数大于等于2个。
  6. innodb_purge_rseg_truncate_frequency:控制 purge 回滚段的频度,默认为128。代表 innodb Purge 操作协调线程 purge事务128次时,就会触发一次 History purge,检查当前的 undo log 表空间状态是否会触发 truncate。

2.1 truncate undo log file 过程

purge 线程进行 truncate undo log file 时,会检查该文件上是否还有活动事务。

如果没有,将 undo log file 标记为不可分配状态,此时新产生的 undo log 将记录到其他文件上,所以至少需要2个独立表空间文件,才能进行 truncate 操作。

当标注不可分配后,会创建一个独立的文件 undo__trunc.log,记录现在正在 truncate 某个 undo log文件。

然后开始初始化 undo log file 到10MB,truncate 完成后删除表示 truncate 动作的 undo__trunc.log 文件。

该文件保证了即使在 truncate 过程中发生了故障宕机重启数据库服务,重启后数据库会继续完成 truncate 操作。

完成 truncate 后删除文件,标识该 undo log file 可分配。

三 存储位置

和 redo log 类似,undo log 也是分布在 buffer 和 磁盘文件中。磁盘中 undo log 默认存放在共享表空间中,但是开启独立表空间(innodb_file_per_table)则会放到对应的各个独立表空间中。

当然也可以使用 innodb_undo_tablespaces 将 undo log 从数据文件中抽离出来,单独存放于 undo 独立表空间,当然 mysql 不建议这样操作。
MySQL - undo log 图文详解_第1张图片

四 DELETE / INSERT操作机制

数据的变更分为两类:1. INSERT 操作在事务提交前只对当前事务可见,所以产生的Undo日志可以在事务提交后直接删除;2. UPDATE、DELETE 操作需要维护多版本信息,两着被归成一类,即 update_undo。

4.1 TRX_UNDO_INSTER_REC

insert 操作对应的 undo 日志类型,进行 insert 操作时,InnoDB会向聚簇索引和所有二级索引都插入一条记录。但是 undo 日志只会记录一条针对聚簇索引的日志。因为聚簇索引记录和二级索引记录是一样的,所以在进行回滚时会根据主键值把聚簇索引和二级索引中相应的记录都删掉。
MySQL - undo log 图文详解_第2张图片

4.2 TRX_UNDO_DEL_MARK_REC

delete 操作对应的 undo 日志类型,删除记录需要进行两个阶段:

  1. delete mark 阶段:将需要删除记录的 deleted_ flag 标识位设置为 1,此时该记录是处于一个中间状态,既不是正常可用记录也不是已删除记录。在事务提交之前都是处于这种状态,用于实现 MVCC。
  2. purge 阶段:在事务提交之后,会有一个 purge 线程将数据删除掉。将该记录从正常记录链表中移除,并移入垃圾链表中。

在两个阶段完成后,一条记录才算真正删除,且占用的空间将可能在之后被重新使用。事务被提交后就不需要回滚了,所以回滚只会存在 delete mark 阶段。

TRX_UNDO_DEL_MARK_REC 类型 undo log 结构如下:
MySQL - undo log 图文详解_第3张图片
从上图中得出:和 TRX_UNDO_INSERT_REC 类型不同点中多了一个 索引列各列信息的内容(len of index_col_info),即某个列被包含在某个索引中,则相关信息就会被记录到这个索引列各列的信息部分。

此处的信息即为:列在记录中的位置(pos)、列占用的存储空间大小(len)、列实际值(value),用于在事务提交后,purge 阶段进行数据删除。

4.3 TRX_UNDO_UPD_EXIST_REC

update 操作需要根据是否更新主键分为不同的情况处理,两种方式的实现是不一样的。

1 不更新主键

就地更新(in-place update)

就地更新即被更新的记录在更新前后占用的存储空间一样大,则可以在原记录上修改。只要有一列更新前后不一样大都不能进行就地更新。

先删再增

当不满足就地更新条件就需要先将该记录删掉,然后在由更新后的值创建一条新的记录。当然主键值是不变的。

需要注意的是:这里的删除不是上面 TRX_UNDO_DEL_MARK_REC 情况,没有两个阶段。而是在更新当前线程进行真正删除。

若更新后的记录大小不超过删掉的原有记录大小,则直接复用原有记录的存储空间。
若更新后的记录大小超过删掉的原有记录大小,则需要申请一块新的空间。若本数据页没有可用的空间,则进行页分裂,在插入更新后的记录。

TRX_UNDO_UPD_EXIST_REC 类型 undo log 结构如下:

图 7.4:

MySQL - undo log 图文详解_第4张图片
n_updated:代表被更新的列信息,<pos,old_len,old_value> 列表中的 pos、old_len 和 old_value 分别表示被更新列在记录中的位置、更新前该列占用的存储空间大小、更新前该列的真实值。
len of index_col_info:若被更新的列包含索引列,则会被添加到此部分。

2 更新主键

如更新语句涉及到主键,则将会进行两个阶段:

  1. 对旧记录进行 delete mark 操作:在事务提交前对旧记录进行且只能执行一个 delete mark 操作,事务提交后在由 purge 线程删除。可能其他事务会使用到改行数据,索引和 TRX_UNDO_DEL_MARK_REC 一样,为了支持 MVCC 是先标记为 delete mark 状态。
  2. 新增新记录:根据更新后的值创建一条记录,因为主键更改了,所以将会根据新的主键值找到应该在聚簇索引中的位置并插入进去。

需要注意的是: 更新主键的情况会记录两条 undo log,即 delete mark 操作时记录一条类型为 TRX_UNDO_DEL_MARK_REC 的 undo log;插入新记录时记录一条类型为 TRX_UNDO_INSERT_REC 的 undo log。

五 DML 操作对二级索引影响

INSERT、DELETE 对二级索引的影响和对聚簇索引是相似的,UPDATE 则不同,若没有涉及二级索引的列,那么就不需要对二级索引执行任何操作。

UPDATE 若涉及到二级索引的列,则和更新主键类似:

  1. 对旧的二级索引记录执行 delete mark 操作。
  2. 根据更新后的值创建一条新的二级索引记录并插入到对应的位置。

六 文件结构

为了保证事务并发操作时,不同事务产生的 undo log 不发生冲突,InnoDB 采用回滚段的方式来维护 undo log 的并发写入和持久化。

undo log 采用分段(segment) 形式记录,rollback segment 称为回滚段,每个回滚段中有1024个 undo log segment。

可通过 innodb_undo_logs 设置回滚段个数,默认 128个。即默认支持同时 128 * 1024 个 undo 操作。

6.1 回滚段(rollback segment)分配规则

默认 128 个,从 resg slot0 到 resg slot127,其中:

  1. slot 0 :预留给系统表空间,位于系统表空间 ibdata 中。
  2. slot 1 ~ 32:预留给临时表空间,每次数据库重启的时候,都会重建临时表空间。
  3. slot 33 ~ 127:如开启独立表空间,则预留给 undo 独立表空间。否则预留给共享表空间。

除去 32 个提供给临时表事务使用,剩下的 128 - 32 = 96 个回滚段。理论可执行 96 * 1024 个并发事务操作,每个事务占用一个 undo segment slot。

但如果事务中有临时表事务,则还会在临时表空间中的 undo segment slot 再占用一个 undo segment slot,即占用 2 个 undo segment slot。

可能在业务十分繁忙的数据库中后出现:Cannot find a free slot for an undo log。说明并发的事务太多了,需要考虑下是否要业务分流, 如常见的分库、分表等等。

根据上面分配规则的描述,可用下图进行展现回滚段基本布局结构:
MySQL - undo log 图文详解_第5张图片

七 崩溃恢复

简单来说:当数据库实例从崩溃中恢复时,需要将活跃的事务从 undo log 中提取出来,对于 ACTIVE 状态的事务直接回滚,对于 Prepare 状态的事务,如果该事务对应的 binlog 已经记录,则提交,否则回滚事务。 这里可以看第六节两阶段提交部分。

你可能感兴趣的:(MySQL,mysql,undo,log)