您的当前位置:首页正文

你想知道undo log的作用都在这里

2024-11-09 来源:个人技术集锦

今天我们来介绍下mysql的undo log 日志,带你更加深入理解它的作用。

01.为什么需要 undo log?

考虑一个问题。一个事务在执行过程中,在还没有提交事务之前,如果mysql发生了崩溃,要怎么回滚到事务之前的数据呢?

如果我们每次在事务执行过程中,都记录下回滚时需要的信息到一个日志里,那么在事务执行中途发生了mysql崩溃后,就不用担心无法回滚到事务之前的数据,我们可以通过这个日志回滚到事务之前的数据。再者当用户用一条rollback语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子。

实现这一机制就是 undo log(回滚日志),它保证了事务的 ACID 特性中的原子性。

undo log除了可以做回滚操作,还有一个作用就是MVCC,当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。

MVCC实现是通过read view + undo log,undo log 为每条记录保存多份历史数据,形成一个版本链。(read view下次讲MVCC的时候再细说)。

02.Undo log版本链

当一直有事务对该行改动时,就会一直生成undo log,最终将会形成undo log版本链。

在讲版本列之前先讲下行的隐藏列有哪些?

在数据库中的每一行上,除了存放真实的数据以外,还存在着3个隐藏列row_id、trx_id与roll_pointer

row_id(行号

如果当前表有整数类型的主键,则row_id就是主键的值。

如果没有整数类型的主键,则mysql会按照字段顺序选择一个非空的整数类型的唯一索引作为row_id。

如果mysql没有找到,则会自动生成一个自动增长的整数作为row_id。

trx_id(事务号)

当一个事务开始执行前,mysql会为这个事务分配一个全局自增的事务id。之后该事务对当前行进行的增、删、改操作时,都会将自己的事务id记录到trx_id中。

roll_pointer(回滚指针

事务对当前行进行改动时,会将旧数据写入进undo log中,再将新数据写入当前行,且当前行的roll_pointer指向刚才那个undo log,因此可以通过roll_pointer找到该行的前一个版本。

我们使用以下语句创建一个person表

CREATE TABLE `person` (  `id` int(11) NOT NULL,  `name` varchar(255) DEFAULT NULL,  `age` int(11) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB

现在开启第1个事务,事务id为1,执行以下插入语句。

INSERT INTO `person`(`id`, `name`, `age`) VALUES (1, '张三', 18);

那么当前行的一个示意图如下:

因为该数据是新插入的,因此它的roll_pointer指向的undo log为空。

接着开启第2个事务,分配到事务id是2,执行以下修改命令。

UPDATE`person` SET `name` = '李四' WHERE `id` = 1

当开启第3个事务,分配到事务id是3,执行以下修改命令。

UPDATE`person` SET `age` = '22' WHERE `id` = 1

每一个事务对该行改动时,都会生成一个undo log,用于保存之前的版本,之后再将新版本的roll_pointer指向刚才生成的undo log。

因此roll_pointer可以将这些不同版本的undo log串联起来,形成undo log版本链

03.undo是逻辑日志还是物理日志?

用户通常对undo有这样的误解,undo用于将数据库物理地恢复到执行语句或事务之前的样子,但实际不是的,undo是逻辑日志因此只是将数据逻辑地恢复到原来的样子。所有修改都被逻辑地取消,但是数据结构和页本身在回滚之后可能大不相同。

比如,一个事务在修改当前一个页中某几条记录,同时还有别的事务在对同一个页中另几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作。

假设用户执行了一个insert 100条记录的事务,这个事务会导致分配一个新的段,即表空间会增大。在用户执行rollback时,会将插入的事务进行回滚,但是表空间的大小并不会因此而收缩。

因此,当事务回滚时,它实际上做的是与先前相反的工作。

比如:

04.undolog怎么样存储?

InnoDB存储引擎对undo的管理同样采用段的方式。InnoDB存储引擎有rollback segment,每个回滚段中记录了1024个undo log segment,在每个undo log segment段中进行undo页的申请。

从InnoDB1.2版本开始,可通过参数对rollback segment做进一步的设置。这些参数包括:

  1. innodb_undo_directory 用于设置rollback segment文件所在的路径。

  2. innodb_undo_logs 用来设置rollback segment的个数,默认值为128。

  3. innodb_undo_tablespaces 用来设置构成rollback segment文件的数量。

    这样rollback segment可以较为平均地分布在多个文件中。

    设置该参数后,会在路径innodb_undo_directory看到undo为前缀的文件,该文件就代表rollback segment文件。

  4. innodb_undo_log_truncate(5.7新增):默认关闭。如开启,当undo超过innodb_max_undo_log_size时,会被truncate到初始化大小,(前提:1:里面的undo不再被使用;2:至少需要2个undo tablespaces)可以通过设置innodb_purge_rseg_truncate_frequency调整truncate频率。

事务在undo log segement分配页写入undo log的这个过程同样需要写入重做日志。当事务提交时,InnoDB存储引擎会做以下两件事:

  1. 将undo log 放入列表中,以供之后的purge操作

  2. 判断undo log所在的页是否可以重用,若可以分配下个事务使用

事务提交后并不能马上删除undo log以及undo log所在的页。这是因为可能还有其他事务需要通过undo log来得到行记录之前的版本。故事务提交时将undo log放入一个链表中,是否可以删除undo log 以及undo log所在的页由purge线程决定(purge线程下回细说)。

innodb引擎设计中对undo页可以进行重用。当事务提交时,首先将undo log 放入链表中,然后判断undo页的使用空间是否小于3/4,若是则表示该undo页可以被重用,之后新的undo log记录在当前的undo log的后面,由于存放undo log的列表是以记录进行组织的,而undo页可能存放着不同事务的undo log,因此purge操作需要涉及磁盘的离散读取操作,是一个比较缓慢的过程。

05.undolog需不需要持久化?

undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。(redo log下回细说)

06.undolog格式

  • insert undo log

  • update undo log

inert undo log是指在insert操作中产生的undo log。因为insert操作的记录,只对事务本身可见,对其他事务不可见,故该undo log可以在事务提交后直接删除。不需要进行purge操作。

update undo log记录的是对delete和update操作产生的undo log。该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undolog链表,等待purge线程进行最后的删除。

总结下undo log 两大作用:

实现事务回滚,保障事务的原子性。事务处理过程中,如果出现了错误或者用户执 行了 rollback 语句,mysql可以利用 undo log 中的历史数据将数据恢复到事务开始之前的状态

实现 MVCC(多版本并发控制)关键因素之一。MVCC 是通过 read view+ undo log 实现的。undo log 为每条记录保存多份历史数据,MySQL 在执行快照读(普通 select 语句)的时候,会根据事务的 read view 里的信息,顺着 undo log 的版本链找到满足其可见性的记录。

END

Top