MySQL日志模块最后一篇——Undo Log回滚日志
文章目录
前言
想象这样一个场景:
一个电商平台的数据库正在经历“双十一”的流量洪峰——
10万用户同时抢购同一款商品,系统既要保证库存扣减的准确性,又要让每个用户实时看到剩余数量;
财务系统在进行跨行转账,交易中途网络突然中断,系统必须瞬间将数据恢复到操作前的状态;
后台运营在生成报表,复杂的统计查询不能影响前台用户的支付操作…
如何在保证数据绝对安全的前提下,让成千上万的读写操作并行不冲突?
答案藏在两个精妙的设计中:
- Undo Log(回滚日志) —— 记录每一次数据变更的“后悔药”。
- MVCC(多版本并发控制) —— 让每个事务拥有独立的数据视角。
本文将深入Undo Log的实现细节,解密MVCC如何通过隐藏事务ID、版本跳跃检测、Read View机制实现高效并发,并通过大量原理图解与实战案例揭示:
- 为什么长事务会导致Undo Log暴涨?
- REPEATABLE READ如何通过版本控制解决幻读?
- 如何从INNODB_TRX表中窥探事务的版本秘密?
一、Undo Log是什么?
Undo Log是InnoDB存储引擎中实现事务原子性和**多版本并发控制(MVCC)**的核心组件。它记录了数据修改前的原始状态,允许事务在失败时回滚到修改前的状态,同时为并发事务提供历史数据版本。
核心特性
- 逆向操作记录: 记录INSERT/DELETE/UPDATE的逆操作。
- 逻辑日志: 不同于物理日志的Redo Log,Undo Log记录逻辑变更。
- 版本链构建: 通过回滚指针形成数据版本链。
- 多事务共享: 不同事务可访问同一数据的不同版本。
二、Undo Log的物理实现
2.1 存储结构
字段名 | 长度 | 说明 |
---|---|---|
Type | 1 byte | 日志类型(INSERT/UPDATE/DELETE) |
Next Log Offset | 2 bytes | 下一条日志位置 |
Transaction ID | 6 bytes | 产生该日志的事务ID |
Rollback Pointer | 7 bytes | 指向上一个版本的Undo Log记录 |
Primary Key Info | variable | 被修改记录的主键信息 |
Old Data | variable | 修改前的数据副本 |
2.2 存储管理
回滚段(Rollback Segment):
InnoDB 通过回滚段管理 Undo Log,每个回滚段包含 1024 个 Undo Log Slot(旧版本仅支持 1 个回滚段)。
- MySQL 8.0 默认创建 128 个回滚段,支持高并发事务的 Undo Log 分配。可通过innodb_rollback_segments调整。
- Undo Log 数据存储在 系统表空间(ibdata) 或 独立的 Undo 表空间(需配置 innodb_undo_tablespaces 参数)。
三、MVCC的实现机制剖析
MySQL 的 MVCC(多版本并发控制) 是一种通过数据多版本实现读写并发的无锁机制,核心依赖 隐藏字段、Undo Log 版本链 和 Read View 一致性视图 实现。这种无锁的方式确保了读不会阻塞写,写不会阻塞读,并且可以保证数据库的一致性。
3.1 隐藏字段
InnoDB为每行数据隐式添加三个关键字段:
DB_TRX_ID(6 字节):记录最后一次修改该行的事务 ID。
DB_ROLL_PTR(7 字节):指向该行在 Undo Log 中的旧版本指针,形成版本链。
DB_ROW_ID(6 字节):隐藏自增行ID(当无主键时自动生成)。
3.2 版本链构建
每次数据修改都会在Undo Log中记录前像(Before Image),并通过回滚指针形成版本链:
结构: 每条数据修改时,旧版本会存入Undo Log,通过 DB_ROLL_PTR 形成单向链表。
- INSERT 操作:Undo Log 记录插入前状态,回滚时删除。
- UPDATE/DELETE 操作:Undo Log 记录旧数据,供 MVCC 和回滚使用。
当前版本 ←(DB_ROLL_PTR)— 版本1 ←(DB_ROLL_PTR)— 版本2 ←(DB_ROLL_PTR)— NULL
3.3 一致性视图(Read View)机制
Read View核心结构
struct ReadView {
trx_id_t m_low_limit_id; // 高水位线:大于等于此ID的事务不可见,判断是否可见
trx_id_t m_up_limit_id; // 低水位线:小于此ID的事务均可见,判断是否可见
trx_id_t m_creator_trx_id; // 创建该视图的事务ID
ids_t m_ids; // 视图创建时的活跃事务ID集合
trx_id_t m_low_limit_no; // 用于Purge的阈值
};
视图生成规则
READ COMMITTED的隔离级别每条SELECT语句执行时创建;REPEATABLE READ的隔离级别第一个SELECT语句执行时创建,他是事务级别的。
版本链遍历流程
3.4 MVCC工作流程
写操作流程
- 获取行排他锁。
- 将当前版本数据写入Undo Log。
- 修改行数据并更新DB_TRX_ID为当前事务ID。
- 设置DB_ROLL_PTR指向刚创建的Undo Log记录。
读操作流程
- 根据隔离级别决定是否创建新Read View。
- 从聚簇索引获取最新行数据。
- 检查该版本的DB_TRX_ID:
- 若符合可见性规则,返回数据。
- 若不可见,沿DB_ROLL_PTR追溯旧版本。
- 遍历至找到第一个可见版本或到达链尾。
3.5 优化建议
控制事务时长: 避免长事务导致版本链过长,阻塞清理(我们可以把长事务拆分)。
-- 关键监控SQL
SELECT
COUNT(*) AS long_trx_count,
MAX(TIMESTAMPDIFF(SECOND, trx_started, NOW())) AS max_duration
FROM information_schema.INNODB_TRX
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;
SHOW STATUS LIKE 'Innodb_history_list_length%';
合理使用索引: 减少回表查询带来的版本检查开销。
监控Undo空间: 定期检查information_schema.INNODB_TRX(专门用于展示 InnoDB 存储引擎中当前运行的所有事务的详细信息)。
避免热点更新: 单行频繁更新会导致版本链退化。
# 控制Undo日志保留时间
# 回滚段(Rollback Segment)的截断频率
innodb_purge_rseg_truncate_frequency=128
# 控制是否开启 Undo 表空间的在线自动截断功能,用于回收空闲的 Undo Log 空间,防止表空间膨胀
innodb_undo_log_truncate=ON
innodb_max_undo_log_size=1G
# Purge线程:异步清理不再需要的旧版本,增加Purge线程数
innodb_purge_threads=4
总结
Undo Log的设计体现了数据库系统的核心哲学:在保证ACID特性的同时,最大限度提升并发性能(无锁)。深入理解其实现机制,不仅有助于优化SQL性能,更能帮助我们开发者设计出更健壮的数据库应用。