MySQL事务日志-Undo Log日志的探究

2.1 Undo Log

2.1.1 Undo Log与原子性

事务的持久性是交由Redo Log来保证,原子性则是交由Undo Log来保证。如果事务中的SQL执行到一半出现错误,需要把前面已经执行过的SQL撤销以达到原子性的目的,这个过程也叫做"回滚",所以Undo Log也叫回滚日志。

Undo Log记录了数据在每个操作前的状态,这些记录包括旧的数据值和事务的 ID。如果事务执行过程中需要回滚,就可以根据Undo Log进行回滚操作。

每当我们要对一条记录做改动时(这里的改动可以指INSERT、DELETE、UPDATE),把回滚时所需的东西记下来。比如:

  • 当执行了一条insert语句时:至少要把这条记录的主键值记下来,之后回滚的时候只需要把这个主键值对应的记录删掉就好了。对于事务中的每个insert语句,在事务回滚时InnoDB都会完成一个delete操作。
  • 当执行了一条delete语句时:至少要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入。对于事务中的每个delete语句,在事务回滚时InnoDB都会完成一个insert操作。
  • 当执行了一条update语句时:至少要把修改这条记录前的旧值都记录下来,这样之后回滚时再把这条记录更新为旧值。对于事务中的每个update语句,在事务回滚时InnoDB都会完成一个反向的update操作。

另外,Undo Log 也是实现多版本并发控制的基础,通过保存旧版本的数据,InnoDB 可以在并发事务中提供隔离级别,如读已提交(Read Committed)、可重复读(Repeatable Read)等。

Tips:Undo Log主要保证事务的原子性,即通过记录修改前的状态,以提供回滚功能,其次Undo Log用于提供MVCC的快照读。

2.1.2 Undo的存储格式

Redo属于物理日志,即记录了数据库页的物理修改操作,比如页上的哪些字节被更改,具体到物理结构上。Undo属于逻辑日志,即记录了从逻辑角度如何撤销已经发生的变更的信息,第一步第二步该如何做等,通常包括了反向操作所需要的数据。

Tips:"Redo属于物理日志"意味着它详细记录了物理层面的数据页是如何被改变的;而"Undo属于逻辑日志"则表示它更多是从逻辑角度出发,记录了为实现某种目的(如回滚或访问历史版本)所需执行的操作步骤。这两者共同保障了MySQL数据库中事务处理的ACID特性。

在InnoDB中,所有表中都会有三个隐藏的列,分别为:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR。

  • DB_TRX_ID:数据行版本号 ,也叫事务ID。当有新的数据修改或插入时的事务ID号,用于记录修改这条记录的事务ID和创建这条记录的事务ID。(记录这条数据是哪个事务修改的,哪个事务创建的)
  • DB_ROLL_PTR:删除行版本号,也叫回滚指针。指向Undo Log中这条记录的上一个版本,删除记录,记录当前事务ID。(记录这条数据是哪个事务删除的)
  • DB_ROW_ID:聚集索引。如果数据表没有主键,InnoDB会创建一个DB_ROW_ID作为聚集索引。

其中事务ID与回滚指针和Undo Log日志密切相关,另外在InnoDB中,Undo Log分为Insert Undo Log和update Undo Log两种类型,其中删除操作也是借助update Undo Log来完成的。

1)insert类型Undo Log

执行如下insert语句:

sql 复制代码
 insert into user values(1,"小灰",20);

当事务开启后,执行的insert语句都会以"insert类型的undo log"记录在undo log日志中,本次事务插入的所有的数据行的版本号字段都为当前事务的ID。

如果需要进行事务回滚,根据undo log中记录的主键进行delete操作即可。

2)delete类型Undo Log

执行如下delete语句:

sql 复制代码
 delete from user where id=1;

当事务开启后,InnoDB对于delete语句的流程是:

  • 1)将被删除的行以"update类型的undo log"记录到undo log日志中。
  • 2)将该行的事务ID设置为当前事务ID,回滚指针设置为被删除前那条记录的事务ID。
  • 3)将删除标记设置为1,表示该记录是被删除掉的记录。
  • 4)更改表空间。

3)update类型Undo Log

执行如下update语句:

sql 复制代码
update user set name="小绿" where id=1;

当事务开启后,InnoDB对于update语句的流程是:

  • 1)将被修改的行以"update类型的undo log"记录到undo log日志中。
  • 2)将该行的事务ID设置为当前事务ID,回滚指针设置为被删除前那条记录的事务ID。
  • 3)更改表空间。

当同一条数据被修改多次,那么Undo Log将通过数据的事务ID和回滚指针能够形成一个非常好的修改链路:

2.1.3 Undo Log的工作原理

InnoDBMySQL启动时,会在内存中构建一个BufferPool,而这个缓冲池主要存放两类东西,一类是数据相关的缓冲,如索引、锁、表数据等,另一类则是各种日志的缓冲,如Undo、Redo....等日志。当一条写SQL执行时,MySQL并不会直接去往磁盘中的ibd文件写数据,而是先修改内存中的Buffer Pool,这样性能就能得到极大的提升。

与Redo Log一样,Undo Log也存在内存缓冲区,即Undo Log Buffer。当一条写SQL执行时,不会直接去往磁盘中的xx.ibdata文件中的Undo Log写数据,而是会写在undo_log_buffer缓冲区中,因为工作线程直接去写磁盘太影响效率了,写进缓冲区后会由后台线程去刷写磁盘。

  • Undo Log完整的工作原理如下:

首先在操作表时,会将表数据从磁盘(.idb)加载到内存中(Buffer),对表的update/delete等操作InnoDB都会事先将修改前的数据备份到Undo Buffer中 ,这样当事务进行回滚时可以根据Undo Buffer中的内容进行事务的回滚操作,除此之外,Undo Buffer提供了数据的快照读取,在事务未提交之前,Undo 日志可以作为并发读写时的读快照,来保证事务的可重复读;

事务做到一半了,失败了,那就要将数据还原到未提交之前的状态,undo 就是记录这些事务步骤的。当然了redo 也记录了,但是redo里面东西太繁杂,不可能什么事都找它(主要是Redo和Undo的格式不一样,应用场景也不一样),于是就将事务步骤写入另外一个地方:undo,以后遇到回滚了就去查找因此在每一步操作时都会写入磁盘中的Undo Log;

引入Undo Log Buffer是来提升Undo Log的性能的,比较操作内存要比操作磁盘快多了,但由此也引入了另外一个问题,那就是既然内存中记录了Undo Log的值,为什么还要在磁盘中也记录Undo Log的值呢?难道Undo Log也要保证持久性?

并不是,在InnoDB中持久性由Redo保证。Undo之所以要写人磁盘是因为InnoDB对数据进行了多版本的控制(MVCC)。

观察如下案例:

session-01 session-02
begin;
begin;
select * from user;
update user set name='小蓝' where id=1;
commit;
select * from user; -- 能否查询到修改的数据?

如果当前的事务隔离级别为RR(可重复读),那么在session-02事务中修改的数据是不能被查询出来。那数据库表中id=1的这一行数据的name值有没有被改为"小蓝"呢?答案是肯定的,因为事务都已经提交了,磁盘表中的name已经修改为了"小蓝"。这就引入了一个问题,并不是提交的数据就一定能被查询出来的,有时候需要查询旧数据。Undo Log正是保存那些旧版本的数据,让其在其他事务中可见。

2.1.4 Undo Log的系统参数

InnoDBundo log的管理采用段的方式,也就是回滚段(rollback segment) 。每个回滚段记录了 1024 个 undo log segment,每个事务只会使用一个回滚段,当一个事务开始的时候,会制定一个回滚段,在事务进行的过程中,当数据被修改时,原始的数据会被复制到回滚段。

在MySQL5.5的时候,只有一个回滚段,那么最大同时支持的事务数量为1024个。在MySQL 5.6开始,InnoDB支持最大 128个回滚段,故其支持同时在线的事务限制提高到了 128*1024 。

我们可以查看InnoDB中Undo Log的有关系统参数,在MySQL5.5之前没有太多参数,如下:

sql 复制代码
 mysql> show variables like 'innodb_max_undo_log_size';
 +--------------------------+------------+
 | Variable_name            | Value      |
 +--------------------------+------------+
 | innodb_max_undo_log_size | 1073741824 |
 +--------------------------+------------+
 1 row in set (0.00 sec)
 ​
 mysql> show variables like 'innodb_rollback_segments';
 +--------------------------+-------+
 | Variable_name            | Value |
 +--------------------------+-------+
 | innodb_rollback_segments | 128   |
 +--------------------------+-------+
 1 row in set (0.00 sec)
  • innodb_max_undo_log_size:本地磁盘文件中,Undo-log的最大值,默认1GB
  • innodb_rollback_segments:指定回滚段的数量,默认为1个。

除开上述两个参数外,其他参数基本上是在MySQL5.6才有的,如下:

sql 复制代码
 mysql> show variables like '%innodb_undo%';
 +--------------------------+-------+
 | Variable_name            | Value |
 +--------------------------+-------+
 | innodb_undo_directory    | ./    |
 | innodb_undo_log_truncate | OFF   |
 | innodb_undo_logs         | 128   |
 | innodb_undo_tablespaces  | 0     |
 +--------------------------+-------+
 4 rows in set (0.00 sec)
  • innodb_undo_directory: 设置rollback segment文件所在的路径。这意味着rollback segment可以存放在共享表空间以外的位置,即可以设置为独立表空间。该参数的默认值为"/",即MySQL的数据文件夹。
  • innodb_undo_logs: 设置rollback segment的个数,默认值为128,也就是之前的innodb_rollback_segments
  • innodb_undo_tablespaces: 设置构成rollback segment文件的数量,这样rollback segment可以较为平均地分布在多个文件中。设置该参数后,会在路径innodb_undo_directory看到undo为前缀的文件,该文件就代表rollback segment文件。
  • innodb_undo_log_truncate:是否开启Undo-log的在线压缩功能,即日志文件超过大小一半时自动压缩,默认OFF关闭。

MySQL5.5版本以后,Undo-log日志支持单独存放,并且多出了几个参数可以调整Undo-log的区域。

2.1.5 Undo Log与Purge线程

前面提到,事务提交后Undo Log日志并不会马上删除,因为其他事务很可能需要用到该数据。直接移除可能会导致其他事务读不到数据。那么对于废弃的undo log日志在什么时候删除呢?另外磁盘表中的被标记为删除的记录(数据空洞),也需要进行空间释放。这些数据都是由MySQL内部的线程------Purge线程来执行后台删除。

  • 针对于insert undo log,因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log在事务提交后就没有用,就会直接删除。
  • 针对于update undo log,该undo log需要支持MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,有专门的purge线程进行删除。

有关于Purge线程的参数:

sql 复制代码
 mysql> show variables like '%purge%';
 +--------------------------------------+-------+
 | Variable_name                        | Value |
 +--------------------------------------+-------+
 | gtid_purged                          |       |
 | innodb_max_purge_lag                 | 0     |
 | innodb_max_purge_lag_delay           | 0     |
 | innodb_purge_batch_size              | 300   |
 | innodb_purge_rseg_truncate_frequency | 128   |
 | innodb_purge_threads                 | 4     |
 | relay_log_purge                      | ON    |
 +--------------------------------------+-------+
 7 rows in set (0.01 sec)
  • innodb_max_purge_lag:当InnoDB存储引擎压力非常大时,Purge线程可能并不会工作,此时是否要延缓DML的操作,innodb_max_purge_lag控制Undo Log的数量,如果数量大于该值,就延缓DML的操作,默认为0,代表不延缓;
  • innodb_max_purge_lag_delay:表示当上面innodb_max_purge_lag的delay超时时间太大,超过这个参数时,将delay设置为该参数值,防止purge线程操作缓慢导致其他SQL线程长期处于等待状态。默认为0,一般不用修改。
  • innodb_purge_batch_size:用来设置每次purge操作需要清理的Undo Log page的数量。
  • innodb_purge_threads:Purge线程的数量(默认为4,最大为32)
相关推荐
小_太_阳13 分钟前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾16 分钟前
scala借阅图书保存记录(三)
开发语言·后端·scala
星就前端叭1 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
YashanDB1 小时前
【YashanDB知识库】XMLAGG方法的兼容
数据库·yashandb·崖山数据库
独行soc2 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍11基于XML的SQL注入(XML-Based SQL Injection)
数据库·安全·web安全·漏洞挖掘·sql注入·hw·xml注入
小林coding2 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者2 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu
从善若水2 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust
风间琉璃""3 小时前
bugkctf 渗透测试1超详细版
数据库·web安全·网络安全·渗透测试·内网·安全工具
drebander3 小时前
SQL 实战-巧用 CASE WHEN 实现条件分组与统计
大数据·数据库·sql