当前位置: 首页 > news >正文

深入剖析MySQL存储架构,索引结构,日志机制,事务提交流程

一、MySQL存储架构(InnoDB)

1. MySQL 整体概览

+----------------------------+
|      客户端/连接层         |
+----------------------------+
|       SQL层(解析/优化)  |
+----------------------------+
|       存储引擎层(InnoDB) |
+----------------------------+
|       文件系统/物理存储    |
+----------------------------+
  • 连接层:管理连接、权限、线程池等。

  • SQL层:包含 SQL 解析器、查询优化器、执行器等。

  • 存储引擎层:真正负责数据的存储、读写、索引维护,如 InnoDB、MyISAM。

  • 物理存储层:最终以文件形式存储在磁盘中,涉及 Page、Block、Redo/Undo 日志文件等。


2. InnoDB 存储引擎架构

InnoDB 是 MySQL 默认的事务型存储引擎,支持 ACID、MVCC、多版本并发控制、行级锁。

1. InnoDB 架构图

+----------------------+
| InnoDB 存储引擎层    |
+----------------------+
| Buffer Pool 缓冲池   |
| Background Threads   |
| Adaptive Hash Index  |
| Doublewrite Buffer   |
| Redo Log Buffer      |
| Change Buffer        |
| Undo Tablespace      |
+----------------------+

3. InnoDB 核心组件详细剖析

1. Buffer Pool(缓冲池)

  • 用于缓存 磁盘上的数据页(Data Page)、索引页Undo页Insert Buffer 页 等。

  • 避免频繁磁盘 I/O。

  • 页大小默认为 16KB。

  • LRU 算法管理淘汰,结合 midpoint insertion strategy 优化读写热点。

2. 数据页结构(Page)

InnoDB 以页(Page)为最小磁盘读写单位,每页大小 16KB,页类型包括:

  • Index Page(索引页)

  • Undo Page(回滚页)

  • Insert Buffer Page

  • LOB Page(大字段)

  • System Page(系统页)

一个表的数据被划分为多个页,这些页组织为 B+ 树。


3. Redo Log(重做日志)

  • 作用:保证崩溃恢复(Crash Recovery),实现 WAL(Write Ahead Logging)。

  • 格式

    • ib_logfile0, ib_logfile1, ...(循环使用)

  • 流程

    • 修改先写入 redo log buffer → 持久化到磁盘 → 数据页后续刷新

即使数据未写入数据页,只要 redo 日志存在,重启后可以恢复数据。


4. Undo Log(回滚日志)

  • 作用

    • 支持事务回滚

    • 实现 MVCC 版本控制(快照读)

  • 存储位置

    • 系统表空间(ibdata)或独立 undo 表空间。

  • 每次更新/删除时生成一条 undo 记录。


5. Doublewrite Buffer(双写缓冲)

  • 作用:防止 “部分页写失败” 导致数据损坏。

  • 流程

    • 页先写入 doublewrite buffer → 再批量刷入数据文件

  • 分布在共享表空间中,大小为 128 页。


6. Change Buffer(变更缓冲)

  • 用于缓存二级索引的插入、更新、删除操作(主键索引不会进入)。

  • 适用于冷数据场景,避免频繁 I/O。

  • 在查询该索引页或刷脏时才应用。


4. 文件结构剖析

1. 数据文件(.ibd)

  • 每个表对应一个 .ibd 文件(独立表空间启用时)。

  • 存储数据页、索引页。

2. 系统表空间(ibdata1)

  • 存放:

    • undo log(未分离时)

    • insert buffer

    • 系统表数据

  • 如果未开启 innodb_file_per_table,数据也存储于此。

3. 重做日志文件(ib_logfileX)

  • 记录 redo log。

  • 通过 innodb_log_file_sizeinnodb_log_files_in_group 控制大小和数量。

4. 表定义文件(.frm / .ibd)

  • .frm(旧版本)记录表结构

  • MySQL 8 后使用数据字典替代 .frm


5. 索引结构剖析

1. 主键索引(Clustered Index)

  • 数据与主键一体存储,叶子节点存放整行数据。

  • 优化主键选择可显著影响性能。

2. 二级索引(Secondary Index)

  • 叶子节点存放主键值而非整行数据,称为回表机制

  • 支持覆盖索引优化(索引包含查询字段时无需回表)。


6. 事务与并发控制机制

1. MVCC 多版本并发控制

  • 利用 undo log 实现。

  • 每行记录有 trx_id, roll_ptr, row_id 等隐藏字段。

2. 隔离级别与实现

隔离级别是否使用 MVCC是否加锁说明
Read Uncommitted读取未提交数据
Read Committed每次读取最新版本
Repeatable Read读取一致快照(默认)
Serializable加锁,强一致

7. InnoDB 后台线程机制

  • Master Thread:协调 flush、merge、purge 等操作。

  • Purge Thread:清理无用 undo 记录。

  • Page Cleaner Thread:将脏页刷入磁盘。

  • I/O Threads:负责后台异步 I/O(读写请求、刷新日志等)。


8. 查询执行与存储交互流程

1. 接收SQL → 解析器解析
2. 优化器生成执行计划
3. 执行器根据计划访问存储引擎
4. InnoDB:- 若页在 buffer pool,直接访问;- 否则从磁盘读取页进入 buffer pool;
5. 若修改数据:- 写入 redo log(先)- 修改 buffer pool 的数据页- 后台刷脏(异步)

9. 小结

模块作用底层机制
Buffer Pool加速读写,减少磁盘 I/OLRU、预读机制
Redo Log崩溃恢复WAL
Undo LogMVCC 与事务回滚支持多版本数据
Doublewrite防止页写入失败二次写入机制
Change Buffer二级索引写入优化延迟合并
索引结构数据组织B+树
多线程机制并发调度与后台任务线程池

二、深入剖析 MySQL InnoDB 页结构(Page Structure) 

1. 页(Page)基本概念

  • InnoDB 是以页(Page)为单位来管理磁盘数据的。

  • 默认页大小为 16KB(16384 字节),可配置为 4KB、8KB、32KB、64KB。

  • 每个表、索引的数据都由多个页组成。

  • 页由段(Segment)管理,段由多个页组成。


2. InnoDB 页类型分类

页类型编号页类型名说明
0FIL_PAGE_INDEXB+树的叶子或非叶子节点页(索引)
2FIL_PAGE_UNDO_LOGUndo 日志页
3FIL_PAGE_INODE段空间页(管理 extent 分配)
4FIL_PAGE_IBUF_FREE_LISTChange Buffer 页
5FIL_PAGE_TYPE_ALLOCATED空闲页
17855FIL_PAGE_COMPRESSED压缩页

我们重点分析最常见的:索引页(FIL_PAGE_INDEX)结构


3. 索引页(FIL_PAGE_INDEX)结构

一页(16KB)大致结构如下图所示:

+-------------------------+ 0
| File Header             | 38 bytes
+-------------------------+ 38
| Page Header             | 56 bytes
+-------------------------+ 94
| Infimum & Supremum      | 26 bytes(13+13)
+-------------------------+ 120
| User Records            | 可变
+-------------------------+ -
| Free Space              | 可变
+-------------------------+ -
| Page Directory          | 2 * N bytes
+-------------------------+ -
| File Trailer            | 8 bytes
+-------------------------+ 16384

4. 各部分详细解析

1. File Header(文件头)- 38 Bytes

位于页起始位置,记录页的一些通用元数据:

字节范围字段名含义
0-1FIL_PAGE_PREV上一页页号(双向链表)
2-3FIL_PAGE_NEXT下一页页号
4-7FIL_PAGE_LSN日志序列号(用于恢复)
24-27FIL_PAGE_TYPE页类型编号(如 0 表示索引页)
38字节共

2. Page Header(页头)- 56 Bytes

描述页内数据的组织方式:

字节范围字段名含义
0-1PAGE_N_DIR_SLOTSPage Directory 的 slot 数量
2-3PAGE_HEAP_TOP当前堆数据使用到的顶端偏移量
4-5PAGE_N_HEAP当前行记录数量(含Infimum/Sup)
6-7PAGE_FREE空闲链表首指针
8-9PAGE_GARBAGE可回收记录的数量(已删除)
26-27PAGE_LEVEL当前页在B+树的层级,0表示叶子页
28-31PAGE_INDEX_ID索引ID

3. Infimum & Supremum Records - 13+13 Bytes

这两个是虚拟记录:

名称说明
Infimum所有记录中最小的值
Supremum所有记录中最大的值

它们并不代表真实数据,用于页面遍历和比较。


4. User Records(记录区域)

用户插入的真实记录,存放在 堆(heap)结构中

每条记录的结构:
+----------------------+------------------------+
| Record Header        | Variable-length fields |
+----------------------+------------------------+
Record Header(变长记录头,5-6字节):
字段名含义
delete_flag标记记录是否被删除(1bit)
min_rec_flag是否最小记录(用于非叶子页)
n_owned本组中有多少条记录(每组小于8)
heap_no堆中记录编号(堆排序)
record_type普通行/Infimum/Supremum/节点指针
next_record下一个记录相对偏移

5. Free Space(空闲空间)

  • 当插入新记录时,使用此区域。

  • 删除记录后形成 gap,会合并/整理形成新的 free space。


6. Page Directory(页目录)

  • 存放用户记录的slot指针。

  • 每个 slot 记录一个 group 的最后一条记录的偏移位置。

  • 每组最多 8 条记录,方便快速查找(类二分)。


7. File Trailer(8 字节)

  • 校验页的完整性(校验和 + LSN)。

  • 用于崩溃恢复、页对比。


5. 页的插入 & 删除操作过程

插入过程:

  1. Free Space 区域分配空间;

  2. 构造 record header + 数据

  3. 插入到堆中,更新指针;

  4. 如果是组尾记录,更新 Page Directory;

  5. 更新 Page Header 的 N_HEAP、HEAP_TOP 等字段。

删除过程:

  1. 设置 delete_flag

  2. 标记为垃圾记录,加入垃圾列表;

  3. 回收在空闲页中复用;

  4. 不立即物理删除,除非触发 purge。


6. 可视化示意图

[ File Header (38B) ]
[ Page Header (56B) ]
[ Infimum ] ---> 最小虚拟行
[ Supremum ] ---> 最大虚拟行
[ User Record 1 ]
[ User Record 2 ]...
[ Free Space ]
[ Page Directory ]
[ File Trailer (8B) ]

7. 页结构优化建议

场景建议
大字段使用 LOB 页避免主页膨胀
热点更新页合理选择主键避免页分裂
插入顺序紊乱考虑逻辑主键、自增主键优化插入
MVCC堆积定期 purge 或重建表(OPTIMIZE)

8. 工具推荐

可用于分析页结构的工具:

  • innodb_ruby:Ruby 编写,分析 .ibd 文件。

  • MySQL Page Inspector

  • Percona Toolkit (pt-ioprofile)

  • hexdump + 自己解析

三、深入剖析 InnoDB 的页分裂(Page Split)触发机制Undo 合并策略

1. 页分裂(Page Split)机制深度剖析

页分裂发生在 InnoDB 的 B+ 树数据页(Leaf Page)或目录页(非叶子页)满了之后,需要插入新记录或维护索引结构时。常见于:

  • INSERT 时页空间不足

  • UPDATE 导致记录变长,占满页空间

  • DELETE 后未及时合并空间


✅ 1. 触发页分裂的典型场景

✅ 场景 A:Insert 插入新记录
  • 页面剩余空间不足(含记录、槽位、页目录等)

  • 插入点在中间位置 → 发生中间分裂(Mid-point split)

  • 插入点在页尾 → 右分裂(Right split)

✅ 场景 B:Update 变更导致行变长(变更变长字段)
  • 原记录为变长字段(如 VARCHARTEXT

  • 更新后值变长,无法原地覆盖

  • InnoDB 会执行:

    • Insert 新记录(新版本)

    • 标记旧记录为删除

    • 如果页已满 → 页分裂


✅ 2. 页分裂流程图(逻辑)

Insert or Update →当前页空间不足?→ 是 → 计算 split 位置→ 分裂出新页(Page X → Page X, Y)→ 调整 B+ 树父节点→ 可能递归触发父节点分裂→ 否 → 原页直接插入/修改

✅ 3. 页分裂代价与影响

  • 增加 I/O 和 CPU

    • 需要新建页、调整 B+ 树索引结构、写 redo log;

  • 容易产生页碎片

    • 若记录偏移或中间插入频繁;

  • 影响查询性能

    • 查询路径变深,减少页缓存命中率;

  • 页合并不主动发生

    • 删除大量数据后不会立即回收空间


✅ 4. 如何避免页分裂

  • 插入数据 尽量顺序写入(按主键自增)

  • 合理设置 innodb_fill_factor

  • 变长字段避免频繁扩展(varchar(200)varchar(4000) 会有巨大开销)

  • 使用 OPTIMIZE TABLE 合并碎片


2. Undo Log 合并策略(Purge 策略)

✅ 1. Undo Log 简述

InnoDB 的 Undo Log 用于:

  • 事务回滚

  • MVCC 构造旧版本数据(快照读)

每次 UPDATE/DELETE 都会生成 Undo Log。


✅ 2. Undo Log 清理场景(Purge)

Undo Log 并不会在事务提交后立刻删除。需要满足两个条件:

条件说明
事务已提交不再需要用于回滚
没有其他事务引用旧版本不再需要用于 MVCC

一旦满足上述条件,Purge 线程将执行 合并 & 回收 Undo Log 记录


✅ 3. Undo 合并策略核心逻辑

for each undo record:if (trx 已提交 && 无快照事务访问该版本):清除 undo record回收 undo segment 空间

底层机制:

  • Purge 线程由后台 srv_purge_coordinator_thread 管理;

  • Undo 日志写入 Undo Tablespace

  • 每个数据页中有 DB_ROLL_PTR 指针指向 undo 记录;

  • 清理时会:

    • 标记 undo 可回收;

    • 移除 undo 页引用;

    • 释放 undo 页或 segment。


✅ 4. Undo 合并触发机制

  • 主动触发

    • 大量 DML(InnoDB 自动判断 Undo 膨胀)

  • 后台周期性任务

    • innodb_purge_threads 个线程清理

  • 手动控制(高版本支持):

    • 设置 innodb_max_purge_lag

    • 或用 PERSIST 参数控制阈值


✅ 5. Undo Log 不清理的风险

问题影响
Undo Log 积压Undo tablespace 不断增长
行版本过多构造 MVCC 成本升高,查询变慢
空间膨胀SSD 容量/磁盘 I/O 受压
查询历史版本变慢二级索引与主键版本一致性差导致性能下降

3. 页分裂 + Undo 的组合风险场景

⚠️ 高频 UPDATE 的热点行:

  • 每次都触发生成新版本行 + Undo Log

  • 原页可能空间不足 → 频繁页分裂

  • Undo 堆积 → Purge 不及时,造成 “版本雪崩”

解决策略:

优化手段效果
避免更新热点字段减少页分裂和 undo 写入
合理设计字段长度降低记录变长的风险
控制大事务、定期提交避免长事务堆积 undo
调整 innodb_purge_threads提升 purge 线程并发

4. 小结图表:页分裂 & Undo 合并对比

维度页分裂Undo 合并
触发时机插入/更新导致页空间不足事务提交后 + 无事务访问旧版本
是否自动是(有后台 purge)
操作对象数据页(索引页)undo log 页
是否影响性能是,增加写放大是,MVCC 增加查询成本
控制参数无明显参数,靠设计优化结构innodb_max_purge_lag 等参数

四、深入剖析 MySQL InnoDB 中更新数据的整个完整流程

我们以:

UPDATE user SET age = 30 WHERE id = 100;

为例,从 SQL 接收 → 存储引擎 → 日志写入 → 缓存修改 → 持久化落盘 的全过程进行逐层深度分析。


1. 整体流程总览图

客户端SQL↓
连接层(连接管理、权限验证)↓
SQL层(解析器 → 优化器 → 执行器)↓
存储引擎(InnoDB)调用接口↓
InnoDB 层执行更新流程:- 定位页(索引查找)- 页进入 Buffer Pool- 生成 Undo Log- 写入 Redo Log Buffer- 修改 Buffer Pool 中的页- 标记为脏页- 后台异步刷盘(Redo Log & 数据页)

2. 详细流程剖析(分阶段)


阶段一:SQL接收与语法解析

✅ SQL 解析与优化
  1. 语法解析器(Parser)

    • 将 SQL 文本解析为语法树。

  2. 查询优化器(Optimizer)

    • 基于 id 走主键索引(Clustered Index)。

    • 生成执行计划。


阶段二:执行器执行 → 调用存储引擎 API

  • 执行器调用 InnoDB 接口:

    handler->update_row()
    

阶段三:InnoDB 执行更新核心流程

✅ 第 1 步:定位数据页(B+树查找)
  • 根据 id=100,在 聚簇索引(Clustered Index)B+ 树中定位对应页(Page)。

  • 若页在 Buffer Pool 中:直接访问。

  • 否则触发 磁盘 I/O,将页从 .ibd 文件读入内存。


✅ 第 2 步:记录 Undo Log(生成回滚日志)

用于:

  • 支持事务回滚。

  • 支持 MVCC(快照读)。

内容包括:

字段
表名user
行主键id=100
修改前值age=28
操作类型UPDATE
trx_id当前事务 ID
  • Undo Log 会写入 Undo Segment(在 Undo Tablespace 中)。


✅ 第 3 步:记录 Redo Log(WAL)

用于崩溃恢复(Crash Recovery)。

  • 记录“将 age 字段从 28 改为 30”的修改意图:

    UPDATE page_id=X offset=Y field=age old=28 new=30
    
  • 写入 Redo Log Buffer,标记 LSN(Log Sequence Number)。

  • 暂时不会立刻刷盘,由后台线程刷盘。

注:即使数据页未落盘,只要 Redo 存在就能恢复(WAL)。


✅ 第 4 步:修改 Buffer Pool 中的数据页
  • 更新 age 字段的值。

  • 修改的是 内存中的页结构

  • 页头字段如 Page_LSNN_HEAP 等也随之修改。

  • 页被打上脏页标记(Dirty Page)。


✅ 第 5 步:更新自带隐藏字段

每行记录都会有隐藏字段:

字段说明
DB_TRX_ID最后修改该行的事务 ID
DB_ROLL_PTR指向 Undo Log 的指针
DB_ROW_ID自动递增行 ID(若无主键)

此时 DB_TRX_ID 会更新为当前事务 ID,DB_ROLL_PTR 指向刚才写入的 Undo Log。


阶段四:事务提交阶段

✅ Step 6:COMMIT 执行
  • 将 Redo Log Buffer 持久化到磁盘(ib_logfile0 等文件)。

  • 顺序:

    1. Redo Log Buffer → Redo Log File
    2. 写入 redo log 的事务被标记为提交完成
    3. 刷新 binlog(若启用)
    
✅ Step 7:二阶段提交(若开启 binlog)
1. Prepare 阶段:写 redo log prepare 状态(未 commit)
2. 写 binlog
3. Commit 阶段:更新 redo log 为 commit 状态

这样 MySQL 就能实现崩溃恢复和 binlog 一致性(XA)


阶段五:后台刷盘

更新事务提交后,数据实际仍可能只在内存中(Buffer Pool)。后台线程会异步处理:

✅ 刷 Redo Log:
  • Log Flusher Thread 周期性写 redo log 到磁盘。

  • 保证 WAL 机制成立。

✅ 刷脏页(Dirty Page):
  • Page Cleaner Thread 会周期性将 Buffer Pool 中的脏页刷到 .ibd 文件。

  • 刷盘位置可不等于事务提交位置(延迟刷盘以提升吞吐)。

✅ 清理 Undo Log:
  • 已提交事务生成的 undo log 会在 Purge Thread 清理后物理删除。


3. 图示总结:更新数据的全流程

+----------------------+
|      SQL层           |
+----------------------+↓
+----------------------+        +------------------+
| 查询执行器 → 调用引擎 | ----> | B+树定位主键页     |
+----------------------+        +------------------+↓                               ↓
+--------------------------+     +-------------------------+
| 页进入 Buffer Pool       |<----|  磁盘 I/O(如未命中)    |
+--------------------------+     +-------------------------+↓
+--------------------------+
| 写 Undo Log(支持回滚) |
+--------------------------+↓
+--------------------------+
| 写 Redo Log(支持恢复) |
+--------------------------+↓
+--------------------------+
| 修改内存页(Buffer Pool)|
+--------------------------+↓
+--------------------------+
| 提交事务 COMMIT         |
|  - Redo 持久化           |
|  - Binlog 写入           |
+--------------------------+↓
+--------------------------+
| 后台线程异步刷脏页       |
+--------------------------+

4. 扩展:若更新的数据页不存在于 Buffer Pool

  • 会发生 页读入(Page Load)

  • 在多并发时,还涉及 页锁(X Lock)

  • 在事务隔离级别为 RR 时,可能读取的是 旧版本行(Undo构造)


5. 小结

模块关键作用
Buffer Pool提高性能,缓存热页
Redo Log崩溃恢复,保证持久性
Undo Log实现 MVCC 与事务回滚
Dirty Page标识内存中已改未刷的新数据页
Page Cleaner异步刷盘,降低写放大
Binlog + Redo XA保证主从/恢复一致性

五、剖析MySQL底层对于updateinsertdelete 的不同点

在 MySQL InnoDB 引擎中,UPDATE、INSERT 和 DELETE 都属于 DML 操作,但三者在底层执行逻辑上存在较大差异。我们从以下几个维度进行深入对比分析:


1. 核心差异总览

对比维度INSERTUPDATEDELETE
操作行为添加新行修改已存在行删除已存在行
B+ 树定位不需要定位已有行(除唯一性校验)需要精准定位待修改记录(通过索引)需要精准定位待删除记录(通过索引)
是否涉及旧版本是(需要记录旧值用于回滚、MVCC)是(需要保留行数据构造历史版本)
Undo Log 类型INSERT Undo(回滚时删除新行)UPDATE Undo(回滚时还原旧值)DELETE Undo(回滚时恢复整行)
Redo Log 内容插入页位置和新值页偏移位置、修改字段和前后值标记删除的记录
Buffer Pool 写入向页尾插入记录修改页内记录字段标记记录为已删除
脏页生成是(插入数据页)是(修改数据页)是(修改页中的 delete_flag)
Purge 清理无需清理(新行已写)有(清理旧版本)有(清理整行记录)

2. 详细底层流程对比

✅ 1. INSERT 流程

SQL:INSERT INTO user(id, name, age) VALUES (1, 'Tom', 18);

底层流程

  1. 判断主键/唯一索引冲突(需要查索引页);

  2. 找到合适的页,在页尾插入新记录;

  3. 写入 Redo Log(插入新值);

  4. 写入 Undo Log(用于回滚:删除新插入的行);

  5. 页入 Buffer Pool,标记为脏页;

  6. COMMIT 时刷 redo log。

⚠️:Undo Log 只在事务未提交前有效。提交后即 purge 可忽略。


✅ 2. UPDATE 流程

SQL:UPDATE user SET age = 30 WHERE id = 1;

底层流程

  1. 使用索引定位到具体页、具体记录;

  2. 记录原值 → 写入 Undo Log(用于回滚 & MVCC);

  3. 写入 Redo Log(记录页位置 + 修改字段 + 新旧值);

  4. 修改 Buffer Pool 中页的数据;

  5. 更新 DB_TRX_IDROLL_PTR 等元信息;

  6. 标记为脏页,等待后台 flush;

  7. COMMIT 时 redo log 写磁盘。

🚨:由于涉及版本控制,Undo Log 会被其他事务用于构造旧版本(MVCC)。


✅ 3. DELETE 流程

SQL:DELETE FROM user WHERE id = 1;

底层流程

  1. 使用索引定位到数据页;

  2. 写 Undo Log(存储整行数据用于回滚/MVCC);

  3. 设置该记录的 delete_flag

  4. 修改 Buffer Pool 页(标记删除);

  5. 写 Redo Log(删除意图);

  6. 后台 purge 线程最终物理删除记录(当无人再访问旧版本)。

❗:DELETE 是“逻辑删除”,物理空间并不会立即释放。


3. Undo Log 类型差异

操作类型Undo 类型Undo 内容
INSERTInsert Undo插入记录主键(回滚时执行 DELETE)
UPDATEUpdate Undo原始字段值(回滚时还原)
DELETEUpdate Undo被删除行所有字段值(回滚时重新插入)

4. MVCC 行为差异(可见性)

操作类型对其他事务影响(快照读)
INSERT不可见(事务未提交前)
UPDATE读取旧版本(通过 Undo 构造)
DELETE读取旧版本(通过 Undo 构造)

5. 刷盘 & Redo Log 写入差异

操作Redo Log 示例
INSERTinsert page X offset Y: record(Z)
UPDATEupdate page X offset Y: age 28→30
DELETEdelete page X offset Y

注意:

  • INSERT redo log 是新增数据;

  • UPDATE redo log 是 delta(变更前后对比);

  • DELETE redo log 是标记记录删除。


6. 性能与空间对比

操作类型性能影响空间占用
INSERT顺序插入性能最好(页合并少)新增记录,占用更多页
UPDATE高并发下性能受版本管理影响多版本 + Undo 空间占用增加
DELETE删除后并不立即回收空间Undo 日志 + 残留记录空间等待清理

7. 小结

场景推荐操作
大量新增操作尽量顺序 INSERT
高并发写场景避免频繁 UPDATE 热点字段
逻辑删除后定期清理历史数据DELETE 后定期 OPTIMIZE TABLE
想保留历史版本使用 UPDATE + MVCC

六、深入剖析 InnoDB 的 MVCC多版本并发控制的实现机制

1. MVCC 是什么?

MVCC 的核心目的是 在不加锁的前提下实现一致性读(Consistent Read),从而:

  • 保证数据读取的一致性(快照读)

  • 实现事务之间的并发控制

  • 提高系统吞吐量(避免读写冲突加锁)

InnoDB 实现的是乐观并发控制机制,即:允许多个事务同时读写,只在提交阶段做冲突检测。


2. InnoDB MVCC 核心机制:依赖三大组件

✅ 1. 行隐藏字段

每一行记录底层都含有两个隐藏字段(物理存储在记录中):

字段名含义
DB_TRX_ID最近一次修改该记录的事务 ID
DB_ROLL_PTR指向 Undo Log 的指针,用于构造旧版本(历史版本链)
逻辑示意:
[最新版本行] ← DB_ROLL_PTR ← Undo 版本1 ← Undo 版本2 ...

✅ 2. Undo Log(撤销日志)

当你执行 UPDATEDELETE 操作时:

  • 原记录不会直接删除,而是写入 Undo Log 中;

  • Undo 存放 变更前的记录内容 + 主键

  • Undo 按事务顺序串联形成一条版本链(version chain);

这条链可以被查询使用,从而构造出任意时间点的数据快照。

✅ 3. Read View(读视图)

  • 每个事务在执行第一个 一致性读SELECT)时,创建自己的 Read View;

  • Read View 记录当前系统中活跃的事务 ID 列表(活跃读写事务);

  • 查询时用来判断某个版本对当前事务是否可见。


3. MVCC 的读取流程(核心)

✅ 事务执行 SELECT 查询的流程如下:

  1. 当前事务生成一个 ReadView,包含以下信息:

    • 当前最大的事务 ID(TRX_ID_MAX

    • 活跃事务 ID 列表(正在执行、未提交)

  2. 每读取一条记录时,InnoDB 判断该记录是否可见:

    • 查看记录的 DB_TRX_ID

    • ReadView 中活跃事务比较

  3. 若该版本不可见,则追溯 DB_ROLL_PTR 指向的 Undo Log,尝试找到“可见版本”

  4. 若追溯后仍没有找到可见版本,则该记录“对当前事务不可见”


✅ 判断可见性的标准

判断条件可见性
记录的 DB_TRX_ID < ReadView 中最小活跃事务 ID✅ 可见
记录的 DB_TRX_ID 属于活跃事务 ID 列表❌ 不可见
记录的 DB_TRX_ID > 当前最大事务 ID(还未存在)❌ 不可见

✅ 举个例子

假设当前有如下事务状态:

事务事务 ID状态
事务A100已提交
事务B(你)105正在运行
事务C110正在运行
事务D115正在运行

你(事务B)在 105 创建了 ReadView,活跃事务列表是 [105, 110, 115],最大事务 ID 是 120:

  • 读到一条记录的 DB_TRX_ID=100(事务A提交时写入) → 可见(已提交、早于你);

  • DB_TRX_ID=110(事务C还没提交) → 不可见;

  • DB_TRX_ID=107(新插入、但尚未提交) → 不可见;

  • DB_TRX_ID=122(以后事务插入的) → 不可见;


4. 不同 SQL 操作下的 MVCC 应用

操作类型MVCC 是否参与说明
SELECT一致性读:通过版本链获取快照
SELECT ... LOCK IN SHARE MODE❌(加锁读)直接读取当前版本,加共享锁
SELECT ... FOR UPDATE❌(加锁读)加排他锁,不走 MVCC
INSERT无需读取旧版本,不走 MVCC
UPDATE生成 Undo 旧版本,保留可回滚和供其他事务可见
DELETE生成 Undo 被删除的版本
COMMIT/ROLLBACK控制版本的可见性,清理 Undo(由 Purge 完成)

5. MVCC 与事务隔离级别关系

隔离级别是否使用 MVCC可见性影响
Read Uncommitted可以读到未提交事务的数据(脏读)
Read Committed每次读都创建新的 ReadView,能看到新提交的数据
Repeatable Read一个事务内只生成一次 ReadView,快照固定
Serializable❌(使用锁)全部加锁,完全串行执行

🔹 InnoDB 默认隔离级别是 Repeatable Read + MVCC


6. 性能优化与注意点

问题原因解决方案
Undo log 越积越多长事务未提交,旧版本无法清除控制事务时长,合理设置 innodb_max_purge_lag
二级索引版本不一致二级索引不存 Undo 指针,仅主键行有版本链回表查询主键获取版本一致性
大字段频繁变更导致 Undo 放大TEXT/BLOB 修改生成大 Undo 记录使用 ROW_FORMAT=dynamic,避免频繁变更大字段

7. 总结:MVCC 实现的核心逻辑图

           ┌──────────────┐│ 当前事务创建 ReadView │└────┬─────────┘↓读取记录 → 比较 DB_TRX_ID↓┌─────────────┬──────────────┐│ 可见版本     │ 不可见 → 追溯 Undo  │└─────────────┴──────────────┘↓返回快照数据 or 不可见

七、深入剖析 MySQL InnoDB 的索引机制和底层数据结构


1. 索引类型分类总览

InnoDB 索引从逻辑上可分为两大类:

类型子类说明
主索引聚簇索引(Clustered)按主键顺序存储数据本身。每张 InnoDB 表只能有一个
辅助索引二级索引(Secondary)存储主键指针(不含行数据)用于回表查询

✅ 主索引与行数据耦合,二级索引仅包含索引列 + 主键(即 RowID)指针。


2. 核心索引结构:B+ 树

InnoDB 使用的是 B+ 树结构(注意不是 B 树),原因在于其高扇出(高阶)特性,适合磁盘 IO 优化

✅ B+树的结构特点

特性描述
所有数据存储在叶子节点非叶子节点仅用于查找导航,不存实际数据
节点间使用双向链表连接支持范围查询、顺序扫描非常高效
每个页称为一个节点(默认 16KB)每个节点可存储多个 key-ptr 对
高扇出(fan-out)减少树高度,提高查找效率

3. 聚簇索引(Clustered Index)

InnoDB 表的主键索引就是聚簇索引。

✅ 特点:

  • 数据行存储在 B+ 树叶子节点中;

  • 叶子节点按主键顺序排序;

  • 整张表的数据即构建在主键 B+ 树上

✅ 举例:

CREATE TABLE user (id INT PRIMARY KEY,name VARCHAR(50),age INT
);

结构示意:

非叶子节点:       [5]         [10]         [15]/   \        /   \         ...
叶子节点:   [1,2,3,4]  [5,6,7,8,9]  [10,...]  ← 含整行记录(id,name,age)

4. 二级索引(Secondary Index)

任何非主键上的索引,InnoDB 都构建为二级索引。

✅ 特点:

  • B+ 树叶子节点中只保存:索引列 + 主键值(即 Row ID);

  • 查询时需回表(用主键回到聚簇索引树中找完整记录);

✅ 举例:

CREATE INDEX idx_name ON user(name);

索引项结构:

叶子节点:
[("Alice", 3),("Bob", 1),("Charlie", 2)
]
→ 通过主键 ID 到聚簇索引中回表

5. 索引项的数据结构(页结构内)

InnoDB 每个节点本质上是一个页(Page,16KB),其物理结构如下:

✅ 页结构层级

┌────────────────────────────┐
│ File Header (38字节)       │
├────────────────────────────┤
│ Page Header (56字节)       │
├────────────────────────────┤
│ Infimum/Supremum (26字节)  │ ← 最小/最大记录,用于边界处理
├────────────────────────────┤
│ User Records (变长)        │ ← 索引记录区域,真正存储 key/value
├────────────────────────────┤
│ Page Directory (变长)      │ ← 快速定位记录位置的槽区
├────────────────────────────┤
│ Free Space                 │ ← 插入新记录使用
├────────────────────────────┤
│ File Trailer (8字节)       │ ← 校验和+页完整性验证
└────────────────────────────┘

✅ 每条记录的结构(Compact Format)

字段含义
Header变长字段指针、null标志、删除标记等
索引字段(主键/辅助键)排序依据
RowID(辅助索引)指向主索引的主键值
事务 ID/Undo PTRMVCC 用于构造版本链

6. InnoDB 索引维护机制(动态调整)

✅ 1. 插入时维护排序(按键值)

  • 插入数据时自动找到对应页;

  • 若页满,触发 页分裂(Page Split);

  • 更新 B+ 树父节点指针,可能递归分裂。

✅ 2. 删除数据触发 页合并(Merge)

  • 删除页中数据后若剩余少量记录;

  • 可与相邻页合并,减少碎片;

  • 同样递归调整 B+ 树结构。


7. 自适应哈希索引(Adaptive Hash Index, AHI)

InnoDB 为了提升热点查询性能,会自动构建内存中的哈希索引:

特性描述
由 InnoDB 自动维护不需要用户干预
哈希范围:前缀或整行匹配不适合范围查询
基于 B+ 树访问频率构建高频查询路径自动加速
构建在 Buffer Pool 上是一种缓存层优化而不是数据结构替代

8. 特殊索引类型说明

索引类型支持 InnoDB说明
BTREE默认索引类型,所有主/辅索引
HASH❌(仅 MEMORY 引擎)不支持范围查询
FULLTEXT✅(5.6+)文本匹配
SPATIAL✅(5.7+)空间数据(GIS)
R-Tree✅(空间索引)专为多维空间数据设计,如经纬度

9. 索引结构与查询性能关系

✅ 覆盖索引(Covering Index)

若查询字段全部在二级索引中,无需回表

SELECT name FROM user WHERE name = 'Alice';

→ 可直接用 idx_name 查询,无需访问聚簇索引页

✅ 最左前缀原则(Prefix Matching)

复合索引 (a,b,c)

  • 支持 (a)(a,b)(a,b,c)

  • 不支持 (b)(b,c) 等跳列查询。

✅ 索引下推(Index Condition Pushdown, ICP)

从 MySQL 5.6 起,过滤条件可在索引页执行,减少回表次数:

SELECT * FROM user WHERE name LIKE 'A%' AND age > 30;

10. 总结:InnoDB 索引机制全景图

InnoDB B+ 树结构:[K5]/        \[K2]             [K8]/   \            /    \
[K1 K2] [K3 K4]   [K6 K7] [K8 K9]聚簇索引:叶子节点保存整行数据
辅助索引:叶子节点保存索引列 + 主键值(回表用)
类型存储内容是否回表是否排序
聚簇索引行数据是(主键)
辅助索引索引列 + 主键值是(索引列)
覆盖索引查询字段全部在索引中

相关文章:

  • Sklearn 机器学习 缺失值处理 填充数据列的缺失值
  • Oracle 客户端深度指南:SQL Developer 与 PL/SQL Developer 全面安装使用教程
  • SpringAI Alibaba实战文生图
  • Python Day45
  • LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
  • 量子计算突破:新型超导芯片重构计算范式
  • Three.js实现梦幻星光漩涡特效 - 从原理到实现
  • 罗尔斯·罗伊斯数字孪生技术赋能航空发动机运维革新:重构维护范式,驱动行业低碳转型
  • Xilinx FPGA 重构Multiboot ICAPE2和ICAPE3使用
  • Linux安装jdk、tomcat
  • SpringBoot离线应用的5种实现方式
  • 【优选算法】前缀和
  • Tableau for mac 驱动
  • 【PhysUnits】15.18 Unit基础结构 (unit.rs)
  • 好未来0520上机考试题1:括号的最大嵌入深度
  • 【PCIe总线】 -- PCI、PCIe相关实现
  • 【Dv3Admin】系统视图下载中心API文件解析
  • 【leetcode】递归,回溯思想 + 巧妙解法-解决“N皇后”,以及“解数独”题目
  • 从零开始制作小程序简单概述
  • C++设计模式 - 单例模式
  • 建设部网站首页督办案件/郑州seo培训
  • 淘宝网站建设情况/西安高端网站建设公司
  • 企业网站诊断与优化方案/信息流优化师发展前景
  • 作作网站/优秀网站设计网站
  • 多语言网站怎么实现的/seo优化快速排名
  • 免费项目管理软件app/太原seo全网营销