MySQL 进阶篇:从存储结构到事务原理全解析

目录

前言

[一、InnoDB 的逻辑存储结构](#一、InnoDB 的逻辑存储结构)

[1.1 表空间(Tablespace)](#1.1 表空间(Tablespace))

[1.2 段(Segment)](#1.2 段(Segment))

[1.3 区(Extent)](#1.3 区(Extent))

[1.4 页(Page)](#1.4 页(Page))

[1.5 行(Row)](#1.5 行(Row))

[二、InnoDB 的整体架构](#二、InnoDB 的整体架构)

[2.1 内存结构](#2.1 内存结构)

[2.1.1 缓冲池(Buffer Pool)](#2.1.1 缓冲池(Buffer Pool))

[2.1.2 更改缓冲区(Change Buffer)](#2.1.2 更改缓冲区(Change Buffer))

[2.1.3 自适应哈希索引(Adaptive Hash Index)](#2.1.3 自适应哈希索引(Adaptive Hash Index))

[2.1.4 日志缓冲区(Log Buffer)](#2.1.4 日志缓冲区(Log Buffer))

[2.2 磁盘结构](#2.2 磁盘结构)

[2.2.1 系统表空间(System Tablespace)](#2.2.1 系统表空间(System Tablespace))

[2.2.2 独立表空间(File-Per-Table Tablespaces)](#2.2.2 独立表空间(File-Per-Table Tablespaces))

[2.2.3 通用表空间(General Tablespaces)](#2.2.3 通用表空间(General Tablespaces))

[2.2.4 撤销表空间(Undo Tablespaces)](#2.2.4 撤销表空间(Undo Tablespaces))

[2.2.5 临时表空间(Temporary Tablespaces)](#2.2.5 临时表空间(Temporary Tablespaces))

[2.2.6 双写缓冲区(Doublewrite Buffer Files)](#2.2.6 双写缓冲区(Doublewrite Buffer Files))

[2.2.7 重做日志(Redo Log)](#2.2.7 重做日志(Redo Log))

[2.3 后台线程](#2.3 后台线程)

[2.3.1 主线程(Master Thread)](#2.3.1 主线程(Master Thread))

[2.3.2 IO 线程](#2.3.2 IO 线程)

[2.3.3 清除线程(Purge Thread)](#2.3.3 清除线程(Purge Thread))

[2.3.4 页清理线程(Page Cleaner Thread)](#2.3.4 页清理线程(Page Cleaner Thread))

[三、InnoDB 的事务实现原理](#三、InnoDB 的事务实现原理)

[3.1 事务的 ACID 特性回顾](#3.1 事务的 ACID 特性回顾)

[3.2 重做日志(Redo Log)------ 保证持久性、一致性](#3.2 重做日志(Redo Log)—— 保证持久性、一致性)

[3.2.1 无 Redo Log 的问题](#3.2.1 无 Redo Log 的问题)

[3.2.2 Redo Log 的工作机制](#3.2.2 Redo Log 的工作机制)

[3.2.3 Redo Log 的设计优势](#3.2.3 Redo Log 的设计优势)

[3.3 回滚日志(Undo Log)------ 保证原子性、一致性](#3.3 回滚日志(Undo Log)—— 保证原子性、一致性)

[3.3.1 事务回滚的实现](#3.3.1 事务回滚的实现)

[3.3.2 Undo Log 的存储与销毁](#3.3.2 Undo Log 的存储与销毁)

[四、InnoDB 的 MVCC 多版本并发控制实现](#四、InnoDB 的 MVCC 多版本并发控制实现)

[4.1 MVCC 的基础概念](#4.1 MVCC 的基础概念)

[4.1.1 当前读(Current Read)](#4.1.1 当前读(Current Read))

[4.1.2 快照读(Snapshot Read)](#4.1.2 快照读(Snapshot Read))

[4.2 MVCC 的三大核心依赖](#4.2 MVCC 的三大核心依赖)

[4.2.1 行的隐藏字段](#4.2.1 行的隐藏字段)

[4.2.2 Undo Log 版本链](#4.2.2 Undo Log 版本链)

[4.2.3 ReadView(读视图)](#4.2.3 ReadView(读视图))

[4.2.3.1 ReadView 的四大核心字段](#4.2.3.1 ReadView 的四大核心字段)

[4.2.3.2 ReadView 的版本链访问规则](#4.2.3.2 ReadView 的版本链访问规则)

[4.2.3.3 不同隔离级别下的 ReadView 生成时机](#4.2.3.3 不同隔离级别下的 ReadView 生成时机)

[4.3 MVCC 的实际执行原理分析](#4.3 MVCC 的实际执行原理分析)

场景假设

执行步骤

快照读的版本匹配过程

五、总结


前言

在 MySQL 的众多存储引擎中,InnoDB 无疑是最核心、最常用的一个。从 MySQL 5.5 版本开始,InnoDB 成为默认存储引擎,它天生支持事务、崩溃恢复、行级锁,还具备优秀的并发处理能力,是企业级开发中处理业务数据的首选引擎。想要真正掌握 MySQL 的优化与内核原理,深入理解 InnoDB 是必经之路。本文将从逻辑存储结构整体架构事务实现原理MVCC 多版本并发控制四个维度,完整解析 InnoDB 的内核细节,做到无一处关键信息遗漏。

一、InnoDB 的逻辑存储结构

InnoDB 的逻辑存储结构是分层设计的,从高到低依次为表空间,每一层都是上一层的组成单元,这种设计让 InnoDB 对数据的管理更高效、更灵活。

1.1 表空间(Tablespace)

表空间是 InnoDB 逻辑存储结构的最高层,用于存储记录、索引等核心数据。MySQL 实例可以对应多个表空间,其管理由参数innodb_file_per_table控制(MySQL 8.0 版本默认开启):

  • 当该参数开启 时,每张表对应一个独立的表空间 ,文件名为xxx.ibd,其中包含该表的表结构、数据和索引;
  • 当该参数关闭 时,所有表的数据都存放在系统表空间(ibdata1)中。

1.2 段(Segment)

表空间由多个段组成,段主要分为数据段索引段回滚段三类,InnoDB 会自动管理段,无需人工干预:

  • 数据段:对应 B + 树的叶子节点 ,因为 InnoDB 是索引组织表,数据最终都存储在索引的叶子节点中;
  • 索引段:对应 B + 树的非叶子节点,用于存储索引的层级结构;
  • 回滚段:用于存储 undo log 日志,为事务回滚和 MVCC 提供支持。

一个段包含多个连续的区,段是 InnoDB 管理磁盘空间的基本单位之一。

1.3 区(Extent)

区是表空间的最小单元结构,每个区的固定大小为 1M 。InnoDB 默认的页大小为 16K,因此一个区包含 64 个连续的页。为了保证页的连续性,InnoDB 每次会从磁盘申请 4-5 个区,避免频繁的磁盘 IO 操作。

1.4 页(Page)

页是 InnoDB磁盘管理的最小单元 ,也是缓冲池缓存的基本单位,默认大小为 16KB。所有的行数据、索引数据最终都会存放在页中,页与页之间通过指针连接,形成链表结构,方便数据的遍历与查找。

1.5 行(Row)

InnoDB 是面向行 的存储引擎,数据最终按行进行存放。除了我们建表时显式定义的字段外,InnoDB 会为每一行自动添加两个默认隐藏字段(无主键时会额外添加一个),这些字段是 InnoDB 实现事务和 MVCC 的核心:

  1. Trx_id:每次对该记录进行改动(插入 / 更新 / 删除)时,会将对应的事务 ID 赋值给该字段;
  2. Roll_pointer:回滚指针,指向该记录修改前的版本(存放在 undo log 中),通过它可以追溯记录的历史版本;
  3. DB_ROW_ID隐藏主键,仅当表中没有显式定义主键时,InnoDB 会自动生成该字段,作为行的唯一标识。

二、InnoDB 的整体架构

InnoDB 的架构整体分为内存结构磁盘结构后台线程 三部分,三者协同工作,实现了 "内存缓存提升性能、磁盘持久化保证数据安全、后台线程异步处理优化效率" 的核心设计思路。其整体架构如下图所示:In-Memory Structures(内存结构) ↔ 后台线程 ↔ On-Disk Structures(磁盘结构)

2.1 内存结构

InnoDB 的内存结构是性能优化的核心,主要包含Buffer PoolChange BufferAdaptive Hash IndexLog Buffer四大核心区域,其中 Buffer Pool 是最核心的部分。

2.1.1 缓冲池(Buffer Pool)

InnoDB 基于磁盘存储,而磁盘 IO 的速度远低于内存,Buffer Pool 的核心作用就是将磁盘上经常被访问的数据加载到内存中,后续的增删改查操作优先操作内存中的数据,避免频繁的磁盘 IO,大幅提升性能。

  • Buffer Pool 以为单位缓存数据,不仅缓存索引页和数据页,还包含 undo 页、锁信息、插入缓存等;
  • 根据页的状态,可将其分为三类:free page(空闲页,未被使用)、clean page(已使用,数据未修改)、dirty page(脏页,已使用,数据被修改,内存与磁盘数据不一致);
  • 性能调优关键参数:innodb_buffer_pool_size,专用服务器上通常将 80% 的物理内存分配给 Buffer Pool。
2.1.2 更改缓冲区(Change Buffer)

Change Buffer 是针对非唯一二级索引页的优化机制。在执行 DML(插入 / 更新 / 删除)操作时,如果目标数据页未加载到 Buffer Pool 中,InnoDB 不会直接操作磁盘,而是将数据变更记录在 Change Buffer 中;当后续该数据页被读取时,再将 Change Buffer 中的变更与内存中的数据页合并,最后一次性刷新到磁盘。

  • 设计原因:二级索引通常是非唯一的,且数据插入是随机的,若每次 DML 都操作磁盘,会产生大量随机 IO;而 Change Buffer 将随机 IO 转化为后续的批量操作,减少磁盘 IO;
  • 注意:唯一索引不支持 Change Buffer,因为唯一索引需要校验唯一性,必须访问磁盘确认数据是否存在,无法利用缓冲区优化。
2.1.3 自适应哈希索引(Adaptive Hash Index)

InnoDB 本身不直接支持哈希索引,但为了提升查询性能,提供了自适应哈希索引功能。哈希索引在等值匹配时的性能远高于 B + 树(一次 IO 即可完成),但不支持范围查询、模糊匹配,而 B + 树则相反。

  • InnoDB 会自动监控表上各索引页的查询情况,若发现哈希索引能提升查询速度,会自动为该索引建立哈希索引,无需人工干预;
  • 自适应哈希索引基于 Buffer Pool 中的数据构建,仅对频繁访问的热点数据生效,是对 B + 树索引的补充优化。
2.1.4 日志缓冲区(Log Buffer)

Log Buffer 是用于临时保存要写入磁盘的 redo log 和 undo log的内存区域,默认大小为 16MB。日志缓冲区的日志会以一定频率异步刷新到磁盘,避免频繁的磁盘 IO。

  • 核心参数 1:innodb_log_buffer_size,用于调整日志缓冲区的大小,若业务中存在大量批量 DML 操作,增大该值可减少磁盘 IO;
  • 核心参数 2:innodb_flush_log_at_trx_commit,控制日志刷新到磁盘的时机,取值有三个(默认值为 1 ,最安全):
    • 1:每次事务提交时,将日志从缓冲区写入并刷新到磁盘,保证事务持久性;
    • 0:每秒将日志写入并刷新到磁盘一次,性能最高,但服务器宕机时会丢失 1 秒内的事务;
    • 2:每次事务提交时将日志写入磁盘,每秒刷新一次,兼顾性能与安全性。

2.2 磁盘结构

InnoDB 的磁盘结构主要用于持久化存储,包含各类表空间、双写缓冲区、重做日志等,是数据安全的最终保障,核心文件均存放在 MySQL 的数据目录下。

2.2.1 系统表空间(System Tablespace)

系统表空间的默认文件为ibdata1,是 InnoDB 的核心系统文件:

  • 存储更改缓冲区的数据、系统级元数据;
  • 在 MySQL 5.x 版本中,还包含数据字典、undo log 等,MySQL 8.0 后部分内容拆分到独立表空间;
  • 由参数innodb_data_file_path控制,默认自动扩展。
2.2.2 独立表空间(File-Per-Table Tablespaces)

由参数innodb_file_per_table控制(8.0 默认开启),每张表对应一个独立的表空间文件 ,文件名为xxx.ibd,该文件中包含了表的结构、数据、索引,是日常开发中最常见的表空间形式。

  • 优势:单表的备份、删除更方便,直接操作对应的.ibd 文件即可;
  • 劣势:每张表都有独立的文件头,会占用少量额外的磁盘空间。
2.2.3 通用表空间(General Tablespaces)

通用表空间是用户自定义的表空间,需要通过CREATE TABLESPACE语法手动创建,创建表时可指定该表空间,多个表可以共享一个通用表空间,兼顾了系统表空间和独立表空间的优点。

复制代码
-- 创建通用表空间
CREATE TABLESPACE ts_name ADD DATAFILE 'file_name' ENGINE = InnoDB;
-- 创建表时指定通用表空间
CREATE TABLE xxx (id INT) TABLESPACE ts_name;
2.2.4 撤销表空间(Undo Tablespaces)

MySQL 实例初始化时会自动创建两个默认的 undo 表空间 (文件名为undo_001undo_002,初始大小 16M),专门用于存储undo log 回滚日志。undo 表空间可以手动扩展,为事务回滚和 MVCC 提供数据支撑。

2.2.5 临时表空间(Temporary Tablespaces)

分为会话临时表空间全局临时表空间 ,文件名为ibtmp1等,用于存储用户创建的临时表、排序临时结果等数据,MySQL 重启后临时表空间的数据会被清空。

2.2.6 双写缓冲区(Doublewrite Buffer Files)

双写缓冲区的默认文件为ib_16384_0.dblwrib_16384_1.dblwr,是 InnoDB 保证数据页写入安全性的核心机制:

  • InnoDB 将数据页从 Buffer Pool 刷新到磁盘前,会先将数据页写入双写缓冲区;
  • 若刷新过程中发生服务器宕机、磁盘故障,导致数据页写入不完整(页损坏),重启后 InnoDB 可从双写缓冲区中读取完整的数据页,恢复到磁盘中,避免数据丢失。
2.2.7 重做日志(Redo Log)

重做日志的默认文件为ib_logfile0ib_logfile1,是实现事务持久性 的核心,采用循环写的方式存储:

  • Redo Log 记录的是数据页的物理修改(如 "某页的某位置修改为某值"),而非逻辑操作;
  • 事务提交时,Redo Log 会从 Log Buffer 刷新到磁盘,即使 Buffer Pool 中的脏页未刷新到磁盘,服务器宕机后,重启可通过 Redo Log 恢复数据,保证事务持久性;
  • 当脏页成功刷新到磁盘后,对应的 Redo Log 就失去了作用,会被新的日志覆盖。

2.3 后台线程

InnoDB 的后台线程是连接内存结构和磁盘结构的桥梁,负责异步处理各类耗时操作 ,避免阻塞用户的业务请求,主要分为Master ThreadIO ThreadPurge ThreadPage Cleaner Thread四类。

2.3.1 主线程(Master Thread)

Master Thread 是 InnoDB最核心的后台线程 ,负责调度其他后台线程,同时承担着数据一致性保障的核心工作:

  • 异步刷新 Buffer Pool 中的脏页到磁盘;
  • 合并 Change Buffer 中的变更到数据页;
  • 回收不再使用的 undo 页;
  • 执行各类定时任务,如刷新日志缓冲区、检查表空间等。
2.3.2 IO 线程

InnoDB 大量使用异步 IO(AIO) 处理磁盘操作,IO Thread 的核心作用是处理异步 IO 的回调请求,将用户请求与磁盘 IO 解耦,提升并发性能。IO Thread 分为四类:

  • Read thread:处理数据页的读请求;
  • Write thread:处理数据页的写请求;
  • Log thread:将 Log Buffer 中的日志刷新到磁盘;
  • Insert buffer thread:将 Change Buffer 中的变更刷新到磁盘。

可通过show engine innodb status \G;查看 IO Thread 的状态信息。

2.3.3 清除线程(Purge Thread)

Purge Thread 的核心工作是回收事务提交后不再使用的 undo log

  • 事务提交后,undo log 不会立即删除,因为可能被 MVCC 的快照读使用;
  • Purge Thread 会异步扫描 undo log,回收那些没有任何事务引用的 undo log,释放磁盘空间。
2.3.4 页清理线程(Page Cleaner Thread)

Page Cleaner Thread 是为了减轻 Master Thread 的工作压力 而设计的,其核心职责是协助 Master Thread 刷新 Buffer Pool 中的脏页到磁盘,避免 Master Thread 因脏页刷新操作阻塞,提升并发性能。

三、InnoDB 的事务实现原理

事务是数据库的核心特性,InnoDB 完美支持事务的ACID 特性 (原子性、一致性、隔离性、持久性)。其中原子性、一致性、持久性Redo Log(重做日志)Undo Log(回滚日志) 保证,隔离性MVCC(多版本并发控制) 共同保证。

3.1 事务的 ACID 特性回顾

  1. 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败;
  2. 一致性(Consistency):事务完成后,所有数据都必须保持一致状态;
  3. 隔离性(Isolation):事务在执行过程中不受其他并发事务的干扰;
  4. 持久性(Durability):事务一旦提交,其对数据库的修改就是永久的,即使服务器宕机也不会丢失。

3.2 重做日志(Redo Log)------ 保证持久性、一致性

Redo Log 是 InnoDB 实现事务持久性的核心,它记录的是数据页的物理修改日志 ,而非业务的逻辑操作。其设计解决了 Buffer Pool 脏页异步刷新带来的持久性问题

3.2.1 无 Redo Log 的问题

Buffer Pool 中的脏页并非实时刷新到磁盘,而是由后台线程异步刷新。如果事务提交后,脏页还未刷新到磁盘,此时服务器宕机,那么该事务的修改就会丢失,违背了事务的持久性。

3.2.2 Redo Log 的工作机制

Redo Log 分为内存中的 Redo Log Buffer磁盘中的 Redo Log File 两部分,其工作遵循WAL(Write-Ahead Logging)写前日志 原则:先写日志,再写磁盘

  1. 执行 DML 操作时,先修改 Buffer Pool 中的数据页(生成脏页),同时将数据页的物理修改记录到 Redo Log Buffer;
  2. 事务提交时 ,将 Redo Log Buffer 中的日志强制刷新到磁盘的 Redo Log File
  3. 后台线程异步将 Buffer Pool 中的脏页刷新到磁盘;
  4. 若脏页刷新过程中发生服务器宕机,重启后 InnoDB 会通过 Redo Log File 恢复数据,保证事务提交的修改不会丢失;
  5. 当脏页成功刷新到磁盘后,对应的 Redo Log 就会被覆盖,因为其使命已经完成。
3.2.3 Redo Log 的设计优势

Redo Log 的写入是顺序写 ,而磁盘上的数据页修改是随机写,顺序写的磁盘 IO 效率远高于随机写。通过 WAL 原则,InnoDB 将高频的随机写转化为低频的顺序写,大幅提升了 DML 操作的性能。

3.3 回滚日志(Undo Log)------ 保证原子性、一致性

Undo Log 是逻辑日志 ,记录的是数据修改前的原始状态 (如执行update操作,Undo Log 记录update的反向操作;执行delete操作,Undo Log 记录insert操作),其核心作用有两个:事务回滚支撑 MVCC 多版本并发控制

3.3.1 事务回滚的实现

当事务执行过程中出现错误,或用户执行rollback命令时,InnoDB 会根据 Undo Log 中的日志,执行反向操作,将数据恢复到事务执行前的状态,保证事务的原子性。

3.3.2 Undo Log 的存储与销毁
  • Undo Log 存储在撤销表空间回滚段中,回滚段内部包含 1024 个 undo log segment,用于管理 Undo Log;
  • 事务提交后,Undo Log不会立即删除,因为可能被 MVCC 的快照读使用,后续由 Purge Thread 异步回收;
  • 对于insert操作的 Undo Log,事务提交后可立即回收,因为insert的记录只有当前事务可见,无其他事务引用。

四、InnoDB 的 MVCC 多版本并发控制实现

在高并发场景下,若仅通过锁实现事务隔离性,会导致大量的锁等待,降低并发性能。InnoDB 通过MVCC(Multi-Version Concurrency Control,多版本并发控制) 实现了非阻塞的快照读,结合锁实现了高效的事务隔离性,是 InnoDB 并发处理的核心设计。

4.1 MVCC 的基础概念

MVCC 的核心是为每条记录维护多个版本 ,不同的事务可以看到记录的不同版本,从而实现读写分离,避免读操作阻塞写操作、写操作阻塞读操作。在理解 MVCC 前,需要先掌握当前读快照读两个概念。

4.1.1 当前读(Current Read)

当前读读取的是记录的最新版本 ,为了保证数据一致性,当前读会对读取的记录加锁,阻止其他并发事务修改该记录。以下操作均为当前读:

  • select ... lock in share mode(加共享锁 S);
  • select ... for update(加排他锁 X);
  • insert/update/delete(加排他锁 X)。
4.1.2 快照读(Snapshot Read)

快照读是不加锁的普通读操作 ,读取的是记录的可见历史版本 ,而非最新版本,是实现非阻塞读的核心。简单的 select 语句(不加锁) 就是快照读,不同隔离级别下快照读的规则不同:

  • 读已提交(Read Committed):每次执行 select,都会生成一个新的快照;
  • 可重复读(Repeatable Read) :开启事务后第一次执行 select时生成快照,后续所有 select 复用该快照(InnoDB 默认隔离级别);
  • 串行化(Serializable):快照读退化为当前读,完全通过锁实现隔离。

4.2 MVCC 的三大核心依赖

InnoDB 的 MVCC 实现依赖于行的隐藏字段Undo Log 版本链ReadView(读视图) 三者的协同工作,这也是 MVCC 的核心底层原理。

4.2.1 行的隐藏字段

如前文所述,InnoDB 为每一行记录添加了三个隐藏字段(无主键时),这三个字段是 MVCC 的基础:

  1. DB_TRX_ID最近修改事务 ID,记录插入 / 最后一次修改该记录的事务 ID;
  2. DB_ROLL_PTR回滚指针,指向该记录修改前的版本(存储在 Undo Log 中);
  3. DB_ROW_ID隐藏主键,表中无显式主键时自动生成,保证行的唯一性。
4.2.2 Undo Log 版本链

当多个事务对同一条记录进行修改时,InnoDB 会为该记录生成Undo Log 版本链 ,链中的每个节点对应记录的一个历史版本,由DB_ROLL_PTR指针连接:

  • 版本链的头部 是记录的最新旧版本尾部 是记录的最早旧版本
  • 每个版本都包含该版本的DB_TRX_ID(修改该版本的事务 ID)和DB_ROLL_PTR(指向上一个版本);
  • insert操作生成的 Undo Log 不会加入版本链,因为其无历史版本;update/delete操作生成的 Undo Log 会构成版本链。
4.2.3 ReadView(读视图)

ReadView 是快照读执行时的依据 ,记录并维护了当前系统中活跃的事务 ID(未提交的事务) 。当执行快照读时,InnoDB 会根据 ReadView 的规则,从 Undo Log 版本链中选择可见的版本返回给用户。

4.2.3.1 ReadView 的四大核心字段
  1. m_ids:当前系统中活跃的事务 ID 集合(未提交的事务);
  2. min_trx_id:当前系统中最小的活跃事务 ID
  3. max_trx_id预分配的事务 ID,即当前系统中最大的事务 ID+1(事务 ID 自增);
  4. creator_trx_id创建该 ReadView 的事务 ID
4.2.3.2 ReadView 的版本链访问规则

设版本链中某版本的事务 ID 为trx_id,根据以下规则判断该版本是否可见:

  1. trx_id == creator_trx_id:可见,该版本由当前事务修改,当前事务可以看到自己的修改;
  2. trx_id < min_trx_id:可见,该版本由已提交的事务修改;
  3. trx_id > max_trx_id:不可见,该版本由 ReadView 创建后才开启的事务修改;
  4. min_trx_id ≤ trx_id ≤ max_trx_id:若trx_id不在m_ids中,可见(事务已提交);否则不可见(事务未提交)。

若当前版本不可见,则通过DB_ROLL_PTR遍历版本链的上一个版本,重复上述判断,直到找到可见版本或版本链结束。

4.2.3.3 不同隔离级别下的 ReadView 生成时机

ReadView 的生成时机是区分读已提交(RC)可重复读(RR) 隔离级别的核心,也是 RR 级别实现可重复读的关键:

  • 读已提交(RC)每次执行快照读时,都会生成一个新的 ReadView;
  • 可重复读(RR)仅在事务中第一次执行快照读时 生成 ReadView,后续所有快照读复用该 ReadView

4.3 MVCC 的实际执行原理分析

结合Undo Log 版本链ReadView 的规则,我们以 InnoDB 默认的可重复读(RR) 为例,分析 MVCC 的执行过程(RC 级别同理,仅 ReadView 生成时机不同)。

场景假设
  1. 存在一条初始记录:id=30, age=30, name=A30,其DB_TRX_ID=1DB_ROLL_PTR=null
  2. 有四个并发事务:事务 2、3、4(执行 DML)、事务 5(执行快照读),事务 ID 依次为 2、3、4、5;
  3. 隔离级别为RR,事务 5 中第一次 select 生成 ReadView,后续复用。
执行步骤
  1. 事务 2 修改该记录(age=3)并提交,生成 Undo Log 版本,记录DB_TRX_ID=2DB_ROLL_PTR指向初始版本;
  2. 事务 3 修改该记录(name=A3)并提交,生成 Undo Log 版本,记录DB_TRX_ID=3DB_ROLL_PTR指向事务 2 的版本;
  3. 事务 5 执行第一次 select (快照读),生成 ReadView:m_ids={4,5}min_trx_id=4max_trx_id=6creator_trx_id=5
  4. 事务 4 修改该记录(age=10)并提交,生成 Undo Log 版本,记录DB_TRX_ID=4DB_ROLL_PTR指向事务 3 的版本;
  5. 事务 5 执行第二次 select (快照读),复用步骤 3 的 ReadView
快照读的版本匹配过程

事务 5 执行 select 时,从 Undo Log 版本链的最新版本开始匹配:

  1. 最新版本:trx_id=4,判断:4在m_ids={4,5}中,不可见,遍历上一个版本;
  2. 上一版本:trx_id=3,判断:3 < min_trx_id=4,可见,停止遍历;
  3. 事务 5 最终读取到的是trx_id=3对应的版本(id=30, age=3, name=A3),即使事务 4 已提交,也无法看到最新版本,实现了可重复读

读已提交(RC) 级别中,事务 5 的第二次 select 会生成新的 ReadView,此时事务 4 已提交,m_ids中无 4,因此可以看到事务 4 的最新版本。

五、总结

InnoDB 引擎的设计是 MySQL 高性能、高可用的核心,其从逻辑存储结构 的分层设计,到内存 + 磁盘 + 后台线程 的架构实现,再到Redo Log/Undo Log 的事务保障,以及MVCC + 锁 的并发控制,每一个细节都围绕着性能数据一致性展开:

  1. 分层的逻辑存储结构让数据管理更高效,索引组织表的设计让数据与索引深度融合;
  2. 内存结构以 Buffer Pool 为核心,结合 Change Buffer、Adaptive Hash Index 等优化,最大化减少磁盘 IO;
  3. 磁盘结构通过各类表空间、双写缓冲区、Redo Log 保证数据的持久化和安全性;
  4. 后台线程实现了异步化的磁盘操作,避免阻塞用户请求,提升并发性能;
  5. Redo Log 和 Undo Log 共同保证了事务的 ACID 特性,WAL 原则大幅提升了 DML 操作的性能;
  6. MVCC 通过隐藏字段、Undo Log 版本链、ReadView 实现了非阻塞的快照读,结合锁让 InnoDB 在高并发场景下兼顾了隔离性和并发性能。
相关推荐
小高不会迪斯科14 小时前
CMU 15445学习心得(二) 内存管理及数据移动--数据库系统如何玩转内存
数据库·oracle
m0_6070766014 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
e***89014 小时前
MySQL 8.0版本JDBC驱动Jar包
数据库·mysql·jar
l1t14 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
青云计划14 小时前
知光项目知文发布模块
java·后端·spring·mybatis
赶路人儿15 小时前
Jsoniter(java版本)使用介绍
java·开发语言
Victor35615 小时前
MongoDB(9)什么是MongoDB的副本集(Replica Set)?
后端
NEXT0615 小时前
二叉搜索树(BST)
前端·数据结构·面试
Victor35615 小时前
MongoDB(8)什么是聚合(Aggregation)?
后端
NEXT0615 小时前
JavaScript进阶:深度剖析函数柯里化及其在面试中的底层逻辑
前端·javascript·面试