MySQL Undo log详解

该文章已生成可运行项目,

一、什么是 MVCC?

MVCC(多版本并发控制,Multi-Version Concurrency Control),是一种数据库并发控制机制。它允许多个事务同时读取数据库的不同版本,实现高并发下的数据一致性与性能优化。

MySQL InnoDB 的 MVCC,主要依赖于 undo log 和 Read View


二、undo log 的作用

undo log(撤销日志)是 InnoDB 存储引擎维护的一种日志,用于记录数据被修改前的旧版本。主要有两个作用:

  1. 事务回滚:如果事务失败或执行 ROLLBACK,可以用 undo log 恢复数据到修改前的状态。
  2. 快照读(一致性读):通过 undo log,可以构造出历史版本,实现 MVCC,让事务看到的是符合其隔离级别的数据快照。

三、undo log 的类型

InnoDB 的 undo log 分为两类:

  1. Insert Undo Log
    记录插入操作,主要用于事务回滚,不用于快照读。
  2. Update Undo Log
    记录更新和删除操作,既用于回滚,也用于快照读。

四、undo log 的结构

每一条 undo log 记录,实际上是对数据行的修改前状态的描述。关键字段有:

  • trx_id:产生该版本的事务 ID。
  • roll_pointer:指向上一个版本的 undo log(形成链表)。
  • 操作类型:INSERT、UPDATE、DELETE。
  • 旧值:被修改前的列值。

数据页中每一行都有两个隐藏字段:

  • trx_id(事务 ID):最后一次修改该行的事务号。
  • roll_pointer(回滚指针):指向该行的上一个版本的 undo log。

这样就能通过 roll_pointer 一步步回溯,找到该行的历史版本。


五、undo log 与 MVCC 的关系

当事务执行一致性读(快照读)时,需要判断数据行版本是否可见:

  • 如果当前行的 trx_id 不满足 Read View 可见性规则,就通过 roll_pointer 找到上一个版本,直到找到可见版本或没有更早版本。

这样就实现了“读旧版本数据”的能力。


六、undo log 的生命周期

  1. 写入:每次行数据被修改(UPDATE/DELETE),会生成 undo log。
  2. 回滚或快照读:需要时可用 undo log 回溯历史版本。
  3. 清理:当没有事务需要这些旧版本(即所有活跃事务的 Read View 都晚于该版本),undo log 会被 purge(清理)。

七、undo log 的存储

  • undo log 不是存储在 redo log 文件里,而是存储在 InnoDB 自己的表空间(默认是 ibdata1)。
  • 8.0 以后支持独立 undo tablespace,便于管理和优化。

八、实际流程举例

假设有如下操作:

  1. T1 开启事务,UPDATE 一条数据(id=1,name=‘A’ 改为 name=‘B’)。
  2. T2 开启事务,做一致性读。

此时:

  • T1 修改数据时,生成一条 undo log,记录 name=‘A’。
  • T2 读取数据时,发现数据行的 trx_id 是 T1 的,但 T1 未提交,T2 的 Read View 不允许看到,于是通过 roll_pointer 找到 undo log,恢复出 name=‘A’,返回给 T2。

九、undo log 与 redo log 的区别

undo logredo log
逻辑日志物理日志
记录数据修改前的旧值记录数据修改后的新值
用于回滚和快照读用于崩溃恢复(持久性)
只影响事务本身影响数据页写入磁盘

十、常见面试/开发问题

  1. undo log 为什么能实现 MVCC?
    因为它保存了历史版本,通过 roll_pointer 可回溯到任何需要的旧版本。

  2. undo log 什么时候会被清理?
    当所有活跃事务的 Read View 都晚于该版本,旧 undo log 就可以被 purge。

  3. undo log 会导致什么问题?
    长事务会导致 undo log 积压,影响性能和空间。

  4. undo log 与快照读的关系?
    快照读依赖 undo log 回溯历史版本,保证数据一致性。


十一、源码相关

  • 主要涉及 trx0undo.cctrx0rseg.ccrow0sel.cc 等文件。
  • undo log 的管理依赖于回滚段(rollback segment)。

十二、调优建议

  • 避免长事务,及时提交,减少 undo log 积压。
  • 合理配置 undo tablespace,避免空间膨胀。
  • 监控 purge 线程状态,保证 undo log 能及时清理。

十三、undo log 的内部结构与链式版本管理

每个被修改的行,都会在记录中维护两个隐藏字段:

  • trx_id:最后一次修改该行的事务ID。
  • roll_pointer:指向这行上一个版本的undo log。

这样,每一行都能通过roll_pointer形成一个“历史版本链”。当一致性读需要回溯旧版本时,InnoDB会沿着roll_pointer链条,逐步找到可见的版本。

举例:

  • 行初始版本trx_id=100,roll_pointer=null。
  • T1(trx_id=101)UPDATE后,行trx_id=101,roll_pointer指向undo log(内容是trx_id=100的旧值)。
  • T2(trx_id=102)UPDATE后,行trx_id=102,roll_pointer指向undo log(内容是trx_id=101的旧值)。
  • 如果有事务的Read View只允许看到trx_id<102的数据,就会通过roll_pointer找到trx_id=101或更早的版本。

十四、undo log 的存储与管理机制

  • undo log存储在哪里?

    • 默认在系统表空间(ibdata1);
    • MySQL 8.0后支持独立undo表空间(undo tablespace),可以配置多个,提升并发和空间管理效率。
  • undo log的清理(purge)机制

    • InnoDB有专门的purge线程,定期清理无用的undo log。
    • 只有所有活跃事务的Read View都不再需要某个undo log时,它才能被安全删除。
    • 如果长事务一直不提交,相关undo log会一直保留,导致空间膨胀和性能下降。

十五、undo log与事务隔离级别的关系

  • READ COMMITTED
    • 每次一致性读都生成新的Read View,undo log能更快被清理。
  • REPEATABLE READ
    • 一个事务期间只生成一个Read View,undo log保留时间更长,适合业务需要“事务期间数据一致”的场景。
  • SERIALIZABLE
    • 所有读都加锁,不走MVCC,也不依赖undo log进行快照读。

十六、undo log 的性能影响与调优

  • 长事务问题

    • 长事务会导致大量undo log无法清理,影响性能和空间。
    • 建议业务上避免长时间未提交的事务,定期监控活跃事务。
  • undo tablespace管理

    • 可以通过参数innodb_undo_tablespaces配置独立undo表空间。
    • 通过SHOW ENGINE INNODB STATUSinformation_schema.innodb_trx监控长事务和undo log积压情况。
  • purge相关参数

    • innodb_purge_threads:设置purge线程数。
    • innodb_max_undo_log_size:限制undo log空间。

十七、undo log的实际应用场景

  1. 回滚操作

    • 事务失败或主动回滚时,InnoDB会用undo log恢复数据到原始状态。
  2. 一致性读(快照读)

    • 事务读取数据时,发现行版本不可见,通过undo log回溯到历史版本,保证读到的数据符合事务隔离级别要求。
  3. 外键约束检查

    • 部分约束校验也会依赖undo log中的历史数据。

十八、undo log相关常见问题

  • 为什么长事务会导致undo log膨胀?

    • 因为purge线程不能清理所有活跃事务可能需要的历史版本,只有事务提交后才可清理相关undo log。
  • undo log和redo log的区别?

    • undo log是逻辑日志,记录修改前的数据,主要用于回滚和MVCC;
    • redo log是物理日志,记录修改后的数据页内容,主要用于崩溃恢复。
  • 如何定位undo log积压问题?

    • 通过SHOW ENGINE INNODB STATUS查看purge相关信息;
    • 查询information_schema.innodb_trx找出长事务。

十九、源码简析

  • 相关核心源码文件:
    • trx0undo.cc:undo log的生成和管理
    • trx0rseg.cc:回滚段(rollback segment)管理
    • row0sel.cc:快照读相关逻辑
  • undo log的结构体:
    • 包含操作类型、旧值、事务ID、指向上一个undo log的指针等。

二十、undo log 的底层实现细节

1. 回滚段(Rollback Segment)与undo log slot

  • 回滚段(rollback segment)是undo log的管理单位。
  • 每个回滚段包含多个 undo log slot,每个slot负责一个事务的undo log链。
  • MySQL 8.0后可以配置多个undo tablespace,每个表空间可以包含多个回滚段,提升并发性能。

2. undo log的写入与链表结构

  • 每次数据修改(UPDATE/DELETE),会把原始数据写入undo log,并将行的roll_pointer指向最新的undo log。
  • undo log本身也是链表结构,每个undo log记录有指针指向上一个版本。

3. undo log的内容

  • 操作类型(插入、更新、删除)
  • 被修改的列及其旧值
  • 事务ID(trx_id)
  • 指向上一个undo log的指针

4. undo log的空间分配

  • undo log空间由undo tablespace分配,空间满后会自动扩展。
  • 可以通过参数innodb_undo_log_truncate开启自动收缩。

二十一、undo log 的清理(purge)流程

  • purge线程定期扫描:查找所有已经不再被任何活跃事务需要的undo log。
  • 清理条件:只有所有活跃事务的Read View都晚于某个undo log对应的trx_id,这个undo log才可以被清理。
  • 清理过程:删除undo log链表节点,释放空间,回收到undo tablespace。

相关参数:

  • innodb_purge_threads:并发purge线程数,建议根据CPU和负载调整(一般2-4)。
  • innodb_max_purge_lag:控制purge滞后,防止大批量积压影响写入性能。

二十二、undo log 与 binlog、redo log 的关系

日志类型作用记录内容主要用途
undo log逻辑日志,回滚/快照读修改前的旧值回滚、MVCC
redo log物理日志,崩溃恢复页的物理修改崩溃恢复
binlog逻辑日志,主从/恢复SQL语句/行变化主从复制、点时间恢复

注意:

  • undo log只影响InnoDB事务,和binlog无直接关系;
  • redo log和undo log配合,保证事务的原子性和持久性。

二十三、undo log 导致的实际问题与排查

1. 长事务导致undo log膨胀

  • 现象:ibdata1或undo tablespace不断膨胀,purge线程清理缓慢,影响性能。
  • 排查:
    • SHOW ENGINE INNODB STATUS 查看History list length(历史版本链长度)。
    • 查询information_schema.innodb_trx,找出长时间未提交的事务。
  • 解决:
    • 优化业务逻辑,避免长事务。
    • 分批处理大数据量操作,及时提交。
    • 增加purge线程数。

2. undo tablespace空间满

  • 现象:DML操作报空间不足错误。
  • 排查:
    • 查看innodb_undo_tablespaces配置,监控磁盘空间。
  • 解决:
    • 增加undo tablespace数量。
    • 合理配置空间大小,开启自动收缩。

3. purge滞后导致写入慢

  • 现象:写入变慢,purge线程忙碌。
  • 排查:
    • 查看SHOW ENGINE INNODB STATUS中的purge相关信息。
  • 解决:
    • 调整innodb_purge_threadsinnodb_max_purge_lag参数。
    • 优化SQL,减少大批量更新。

二十四、undo log 的监控与运维建议

  • 定期监控长事务:通过information_schema.innodb_trx,及时kill异常长事务。
  • 监控历史链长度SHOW ENGINE INNODB STATUS中的History list length,一般保持在数千以内。
  • 合理配置undo tablespace:根据业务量和磁盘空间,配置合适数量和大小。
  • 优化业务逻辑:避免大事务、长事务,推荐分批处理。
  • 升级MySQL版本:新版本支持undo tablespace自动收缩、独立表空间,便于管理。

二十五、undo log 的SQL演示

-- 创建表
CREATE TABLE t1 (id INT PRIMARY KEY, val VARCHAR(20)) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1, 'A');

-- 开启事务A
START TRANSACTION;
UPDATE t1 SET val = 'B' WHERE id = 1;

-- 在另一个会话开启事务B
START TRANSACTION;
SELECT * FROM t1; -- 看到的是'A',不是'B',因为B的Read View不可见A未提交的修改

-- 提交事务A
COMMIT;

-- 事务B再次查询
SELECT * FROM t1; -- 在REPEATABLE READ下还是'A',在READ COMMITTED下变成'B'

此过程中,undo log保存了原始值’A’,供事务B快照读使用。


二十六、undo log 的源码流程简述

  • 数据修改时,调用row_update_for_mysql(),生成undo log。
  • undo log写入undo tablespace,更新roll_pointer。
  • purge线程周期性调用trx_purge()清理无用undo log。
  • 一致性读时,row_search_mvcc()根据Read View判断是否需要回溯undo log链。

二十七、总结

undo log 是 InnoDB MVCC 的基石,配合 Read View 实现高效的多版本并发控制。它让事务能安全地回滚,也能让读操作获得一致性快照,是实现高并发、高可靠性的关键机制。

  1. undo log是InnoDB MVCC的核心,保证了高并发下的数据一致性和事务可靠性;
  2. 合理管理undo log,避免长事务,是数据库性能优化的重要环节;
  3. undo log与Read View配合,让MySQL实现了高效的多版本并发控制。
本文章已经生成可运行项目
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猩火燎猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值