一、什么是 MVCC?
MVCC(多版本并发控制,Multi-Version Concurrency Control),是一种数据库并发控制机制。它允许多个事务同时读取数据库的不同版本,实现高并发下的数据一致性与性能优化。
MySQL InnoDB 的 MVCC,主要依赖于 undo log 和 Read View。
二、undo log 的作用
undo log(撤销日志)是 InnoDB 存储引擎维护的一种日志,用于记录数据被修改前的旧版本。主要有两个作用:
- 事务回滚:如果事务失败或执行
ROLLBACK,可以用 undo log 恢复数据到修改前的状态。 - 快照读(一致性读):通过 undo log,可以构造出历史版本,实现 MVCC,让事务看到的是符合其隔离级别的数据快照。
三、undo log 的类型
InnoDB 的 undo log 分为两类:
- Insert Undo Log
记录插入操作,主要用于事务回滚,不用于快照读。 - 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 的生命周期
- 写入:每次行数据被修改(UPDATE/DELETE),会生成 undo log。
- 回滚或快照读:需要时可用 undo log 回溯历史版本。
- 清理:当没有事务需要这些旧版本(即所有活跃事务的 Read View 都晚于该版本),undo log 会被 purge(清理)。
七、undo log 的存储
- undo log 不是存储在 redo log 文件里,而是存储在 InnoDB 自己的表空间(默认是
ibdata1)。 - 8.0 以后支持独立 undo tablespace,便于管理和优化。
八、实际流程举例
假设有如下操作:
- T1 开启事务,UPDATE 一条数据(id=1,name=‘A’ 改为 name=‘B’)。
- 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 log | redo log |
|---|---|
| 逻辑日志 | 物理日志 |
| 记录数据修改前的旧值 | 记录数据修改后的新值 |
| 用于回滚和快照读 | 用于崩溃恢复(持久性) |
| 只影响事务本身 | 影响数据页写入磁盘 |
十、常见面试/开发问题
-
undo log 为什么能实现 MVCC?
因为它保存了历史版本,通过 roll_pointer 可回溯到任何需要的旧版本。 -
undo log 什么时候会被清理?
当所有活跃事务的 Read View 都晚于该版本,旧 undo log 就可以被 purge。 -
undo log 会导致什么问题?
长事务会导致 undo log 积压,影响性能和空间。 -
undo log 与快照读的关系?
快照读依赖 undo log 回溯历史版本,保证数据一致性。
十一、源码相关
- 主要涉及
trx0undo.cc、trx0rseg.cc、row0sel.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 STATUS和information_schema.innodb_trx监控长事务和undo log积压情况。
- 可以通过参数
-
purge相关参数
innodb_purge_threads:设置purge线程数。innodb_max_undo_log_size:限制undo log空间。
十七、undo log的实际应用场景
-
回滚操作
- 事务失败或主动回滚时,InnoDB会用undo log恢复数据到原始状态。
-
一致性读(快照读)
- 事务读取数据时,发现行版本不可见,通过undo log回溯到历史版本,保证读到的数据符合事务隔离级别要求。
-
外键约束检查
- 部分约束校验也会依赖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_threads和innodb_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 实现高效的多版本并发控制。它让事务能安全地回滚,也能让读操作获得一致性快照,是实现高并发、高可靠性的关键机制。
- undo log是InnoDB MVCC的核心,保证了高并发下的数据一致性和事务可靠性;
- 合理管理undo log,避免长事务,是数据库性能优化的重要环节;
- undo log与Read View配合,让MySQL实现了高效的多版本并发控制。


被折叠的 条评论
为什么被折叠?



