【MySQL数据库】InnoDB实现MVCC(多版本并发控制)底层原理

InnoDB实现MVCC(多版本并发控制)底层原理


一、Undo Log版本链

Undo Log版本链是InnoDB实现MVCC(多版本并发控制)的核心基础之一,本质是一行数据被多次修改后,由各版本的Undo Log记录串联形成的链式结构,用于保存数据的历史版本,支撑事务的一致性读和回滚需求。

其核心逻辑可拆解为3点:

  1. 版本记录来源 :当事务修改一行数据时(INSERT/UPDATE/DELETE),InnoDB会先将修改前的旧数据写入Undo Log(如UPDATE时,记录"修改前的字段值、事务ID、指向更早版本的指针"),同时在数据行的隐藏列(如DB_TRX_ID记录修改事务ID、DB_ROLL_PTR记录指向当前版本Undo Log的指针)中关联该Undo Log。
  2. 链式串联逻辑:每次数据被新事务修改,都会生成新的Undo Log条目,新条目中的"前驱指针"会指向之前版本的Undo Log,最终形成一条从"最新数据行"反向指向"最早历史版本"的链表(即版本链)。
  3. 核心作用:事务执行快照读(如普通SELECT)时,会基于ReadView(可见性规则)遍历这条版本链,找到符合"当前事务可见"的历史版本,避免加锁;同时,若事务需要回滚,也可通过版本链恢复到修改前的状态。

二、如何通过Undo Log版本链来实现数据库的事务回滚?

通过Undo Log版本链实现事务回滚,核心逻辑是利用版本链中记录的"数据修改前的历史状态",反向撤销当前事务对数据的所有修改操作,最终将数据恢复到事务开始前的初始状态。其具体过程可拆解为"回滚触发条件""核心执行步骤"和"关键细节补充"三部分,如下所示:

一、回滚的触发条件

事务回滚并非主动执行,需满足以下场景之一:

  1. 事务主动发起回滚:开发者显式执行 ROLLBACK 语句(如代码中捕获异常后触发回滚)。
  2. 事务被动回滚:数据库因异常强制终止事务,例如:
    • 事务执行过程中出现错误(如主键冲突、SQL语法错误);
    • 数据库崩溃、断电等硬件/软件故障;
    • 事务超时被数据库强制终止;
    • 并发事务冲突导致的回滚(如乐观锁校验失败)。

二、基于Undo Log版本链的回滚核心步骤

InnoDB会为每个事务维护一个事务私有Undo Log链表(记录该事务所有修改操作对应的Undo Log),回滚时通过遍历这个链表,结合版本链中的历史数据反向恢复,具体步骤如下:

步骤1:定位事务的私有Undo Log链表

每个事务启动时,InnoDB会为其分配唯一的TRX_ID(事务ID),并在事务执行修改操作(INSERT/UPDATE/DELETE)时,将对应的Undo Log条目标记为"归属该TRX_ID",最终形成一条仅包含当前事务修改记录的Undo Log链表(可理解为"事务的修改操作日志清单")。

回滚触发时,InnoDB首先根据当前事务的TRX_ID,找到这条专属的Undo Log链表,且遍历顺序是**"从后往前"**(即先撤销最后一次修改,再撤销倒数第二次,直到事务开始前)。


[ 补充 TRX_ID(事务ID) 概念 ]

TRX_ID(事务ID)是InnoDB存储引擎为每个事务分配的唯一标识,是事务并发控制与数据版本管理的核心"身份凭证",核心作用与特性可简要解析为3点:

  1. 分配规则:事务启动并首次执行修改操作(INSERT/UPDATE/DELETE)时,InnoDB会生成一个自增的TRX_ID分配给该事务(只读事务可能不分配),确保每个事务的标识唯一,避免版本混淆。

  2. 核心用途

    • 标记数据版本归属:数据行的隐藏列DB_TRX_ID会记录"最后修改该行的事务TRX_ID",用于判断数据版本与当前事务的关联关系;
    • 支撑MVCC可见性判断:事务生成ReadView时,会包含当前活跃事务的TRX_ID列表,结合数据行的DB_TRX_ID,判断该数据版本是否对当前事务可见;
    • 定位事务私有Undo Log:回滚时,InnoDB通过TRX_ID找到该事务专属的Undo Log链表,确保只撤销当前事务的修改操作。
  3. 与事务一致性的关联:TRX_ID的自增特性和唯一标识性,保证了InnoDB能精准追溯数据修改的"发起者",无论是回滚时定位修改记录,还是MVCC中筛选可见版本,都依赖TRX_ID实现"精准匹配",最终支撑事务的原子性、隔离性。


步骤2:按操作类型反向撤销(核心:利用版本链恢复历史数据)

不同修改操作(INSERT/UPDATE/DELETE)的回滚逻辑不同,均依赖Undo Log版本链中记录的"历史版本指针"和"旧数据":

原操作类型 Undo Log中记录的关键信息 回滚逻辑(反向撤销)
INSERT 新插入行的主键、DB_TRX_ID(当前事务ID) 直接删除该插入行(因插入行仅当前事务可见,删除后无并发影响)
UPDATE 1. 修改前的旧字段值 2. 指向"修改前版本Undo Log"的指针(DB_ROLL_PTR) 3. 被修改行的主键 从Undo Log中读取"修改前的旧字段值",将当前数据行的字段恢复为旧值;同时更新数据行的隐藏列(DB_TRX_ID设为回滚事务ID,DB_ROLL_PTR指向原历史版本的Undo Log),确保版本链完整性
DELETE 1. 删除前的完整行数据(含所有字段值) 2. 指向"删除前版本Undo Log"的指针 3. 被删除行的主键 InnoDB的DELETE并非物理删除,而是标记行的"删除位"(DB_FLAG);回滚时只需清除"删除位",并将Undo Log中记录的"删除前完整数据"写回行中,同时恢复DB_TRX_IDDB_ROLL_PTR,让该行重新可见
步骤3:释放Undo Log资源(区分"事务回滚"与"事务提交")

回滚完成后,需处理Undo Log的生命周期:

  • 对于INSERT操作的Undo Log:回滚后可直接删除(因插入行已被删除,该Undo Log无后续用途);
  • 对于UPDATE/DELETE操作的Undo Log:需判断是否被其他事务的"快照读"依赖(即其他事务的ReadView是否仍需要该历史版本)。若无人依赖,可标记为"可回收",后续由InnoDB的Purge线程异步清理;若仍被依赖,则暂时保留,避免影响其他事务的一致性读。

三、关键细节补充:回滚与版本链的联动

  1. 回滚不破坏版本链完整性

    回滚过程中,恢复数据时会同步更新数据行的隐藏列(DB_TRX_IDDB_ROLL_PTR),确保版本链始终是"最新数据→历史版本"的完整链表。例如:事务A修改行X生成版本2,回滚时恢复为版本1,此时行X的DB_ROLL_PTR会重新指向版本1的Undo Log,版本链仍保持"行X(当前)→版本1 Undo Log→更早版本"的结构。

  2. 回滚是"事务级原子操作"

    即使事务包含多个修改操作(如先UPDATE再INSERT),回滚也会"要么全部撤销,要么全部不撤销"------InnoDB通过事务的ACID特性保证回滚的原子性,若回滚过程中出现故障(如断电),重启后会通过事务日志(Redo Log)恢复到回滚前的状态,再重新执行回滚,避免数据不一致。

  3. 与Redo Log的协同

    回滚过程本身也会产生Redo Log(例如:回滚UPDATE时"恢复旧值"的操作,会被记录到Redo Log中)。这是因为若回滚过程中数据库崩溃,重启后可通过Redo Log重演"回滚操作",确保回滚本身的持久性,避免数据处于"半回滚"状态。

总结

Undo Log版本链为事务回滚提供了"可恢复的历史数据基础"------通过记录每次修改的"旧版本",回滚时只需反向遍历事务的Undo Log链表,按操作类型恢复数据即可。整个过程既保证了事务的原子性(要么全提交,要么全回滚),又不破坏MVCC依赖的版本链结构,是InnoDB事务一致性的核心支撑。

相关推荐
qq_172805592 小时前
好用的 SQLite3 ORM 开源库
数据库·sqlite·开源
jackletter2 小时前
五大关系数据库(sqlserver、mysql、oracle、pgsql、sqlite)的对象名称和转义字符
mysql·postgresql·oracle·sqlserver·sqlite
AWS官方合作商2 小时前
打破数据枷锁:在AWS上解锁Oracle数据库的无限潜能
数据库·oracle·aws
会飞的架狗师3 小时前
【MySQL体系】第2篇:MySQL索引类型和原理
数据库·mysql
gsfl3 小时前
Redis List 类型全解析
数据库·redis·list
lang201509283 小时前
深度解析MySQL InnoDB缓冲池性能优化
数据库·mysql
名誉寒冰10 小时前
MySQL索引原理解析:为什么选择B+树?
数据库·b树·mysql
我是苏苏11 小时前
Redis开发07:使用stackexchange.redis库实现简单消息队列
数据库·redis·缓存