Undo log在MySQL事务的实现中也起着至关重要的作用,MySQL中事务的一致性是由Undo Log实现的。Undo log在MySQL事务的实现中主要起到两方面的作用:回滚事务和多版本并发事务,也就是我们常说的MVCC机制。
1 基本概念
MySQL在启动事务之前,会先将要修改的数据记录存储到Undo Log中。如果数据库的事务回滚或者MySQL数据库崩溃,可以利用Undo Log对数据库中未提交的事务进行回滚操作,从而保证数据库中数据的一致性。
Undo Log会在事务开始前产生,当事务提交时,并不会立即删除响应的Undo Log。此时,InnoDB引擎会将当前事务对应的Undo Log放入待删除的列表,接下来,接下来,通过一个后台线程purge thread进行删除处理。
Undo Log和Redo Log不同,Undo Log记录的是逻辑日志,可以这样理解:当数据库执行一条insert语句时,Undo Log会记录一条对应的delete语句;当数据库执行一条Update语句时,Undo Log会记录一条相反的update语句。
需要注意的是,因为MySQL事务执行过程中产生的Undo Log也需要进行持久化操作,所以Undo Log也会产生Redo Log。由于Undo Log的完整性和可扩展性需要Redo Log来保证,因此数据库崩溃时需要先做Redo Log数据恢复,然后再做Undo Log回滚。
2 存储方式
2.1 系统表空间存储(传统方式)
- 默认位置:早期版本中,Undo Log 默认存储在系统表空间 ibdata1 文件中
- 特点:
- 所有 undo log 共享同一存储空间
- 容易造成 ibdata1 文件过大且无法收缩
- 长期运行后可能导致磁盘空间问题
2.2 独立 Undo 表空间(MySQL 5.6+)
通过innodb_undo_tablespaces变量可以将回滚段平均分配到多个文件中。innodb_undo_tablespaces变量默认是0,表示将回滚段全部写到同一个文件中。
需要注意的是,innodb_undo_tablespaces变量只能在停止MySQL服务的情况下进行修改,重启后生效,不过不建议修改这个值。
- 配置参数:
innodb_undo_directory = /path/to/undo # 指定存储目录 innodb_undo_tablespaces = 4 # 设置undo表空间数量 innodb_undo_logs = 128 # 设置undo log段数量
- 文件命名:默认为
undo001
到undoN
(N为配置的数量) - 特点:
- 每个表空间大小默认约10MB
- 支持在线 truncate(收缩)
- 避免系统表空间膨胀问题
2.3 Undo Log 物理结构
- 页结构:使用特殊的 undo 页(16KB大小)
- 段管理:
- 每个事务使用独立的 undo 段
- 系统维护回滚段(rollback segment),每个回滚段包含多个 undo slot
- 链式存储:同一记录的多次修改形成版本链
2.4 现代版本改进(MySQL 8.0+)
- 默认启用独立Undo表空间
- 自动截断机制:
innodb_undo_log_truncate = ON innodb_max_undo_log_size = 1G # 触发truncate的阈值
- 临时Undo日志:MySQL 8.0引入,将临时表的undo日志与常规undo日志分离
2.5 存储内容格式
- INSERT操作:存储主键信息,回滚时执行删除
- DELETE操作:存储完整记录,回滚时执行插入
- UPDATE操作:存储被修改前的内容,回滚时恢复旧值
2.6 空间回收机制
- 事务提交后,undo log 不会立即删除
- 当没有活跃事务需要访问旧版本时,空间可被回收
- 通过后台 purge 线程清理不再需要的 undo log
3 MVCC机制与Undo Log的关系
3.1 MVCC 如何利用Undo Log
当执行快照读(普通 SELECT) 操作时,MVCC 会按照以下步骤来获取数据的适当版本:
- 确定事务的可见性:每个事务在启动时,都会生成一个一致性视图(consistent read view),这个视图包含了当前活跃事务的列表。
- 版本链追溯:从数据的当前版本开始,依据 DB_ROLL_PTR 指针不断追溯 undo log,直到找到满足可见性条件的版本。
- 判断版本是否可见:通过比较 DB_TRX_ID 和一致性视图,来判断该版本是否对当前事务可见。
3.2. 一个实际例子
假设有如下的操作流程:
- 事务 T1将数据从 A 修改为 B,此时会生成一条 undo log,记录原来的值 A。
- 事务 T2(在 T1 提交之后启动)将数据从 B 修改为 C,同时生成一条 undo log,记录原来的值 B。
- 当事务 T3(在 T2 修改之后启动)执行快照读时:
- 它首先读取到的数据是 C(当前版本),其 DB_TRX_ID 为 T2。
- 如果 T2 在 T3 启动时还处于活跃状态,那么 T3 会通过 DB_ROLL_PTR 追溯到 undo log 中的 B 版本。
- 如果 B 版本的 DB_TRX_ID 对 T3 可见,那么 T3 就会读取到 B 这个值。
3.3. 关键优势
- 无锁读:通过 MVCC 和 undo log,SELECT 操作无需加锁,从而提高了并发性能。
- 实现事务隔离:在 REPEATABLE READ 隔离级别下,一个事务在执行期间读取到的数据版本是一致的。
- 回滚支持:undo log 能够保证事务可以原子性地回滚。