【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事务一致性的核心支撑。

相关推荐
草明2 分钟前
clickhouse 检查是否有删除语句在执行
数据库·clickhouse
风语者日志14 分钟前
攻防世界—easyupload
数据库·web安全·ctf·小白入门
SamDeepThinking17 分钟前
MySQL 8 查询缓存已废除详解:从架构、历史到替代方案
mysql
彡皮25 分钟前
qt实用学习案例:数据库设计+图表显示+model-view模式+样式表定制
数据库·qt·学习
SamDeepThinking1 小时前
MySQL 8 索引与 B+ 树-初浅理解
mysql
码出钞能力1 小时前
如何屏蔽GORM个别sql的日志
数据库·sql·gorm
TDengine (老段)1 小时前
TDengine 数字函数 RADIANS 用户手册
大数据·数据库·sql·物联网·时序数据库·tdengine·涛思数据
小蒜学长1 小时前
springboot基于JAVA的二手书籍交易系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端
野犬寒鸦2 小时前
从零起步学习MySQL || 第七章:初识索引底层运用及性能优化(结合底层数据结构讲解)
java·数据库·后端·mysql·oracle
6极地诈唬2 小时前
【sqlite】WAL初探
数据库·sqlite