Skip to content

InnoDB存储引擎


MySQL 的 InnoDB 存储引擎是 MySQL 数据库中最常用、功能最强大的事务型存储引擎之一。自 MySQL 5.5 版本起,InnoDB 成为 MySQL 的默认存储引擎,取代了之前的 MyISAM。InnoDB 以其对 ACID 事务的支持、行级锁定、崩溃恢复能力以及外键约束等特性,在高并发、高可靠性的应用中被广泛采用。

InnoDB 完全支持 ACID(原子性、一致性、隔离性、持久性)事务特性:

  1. 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败回滚。
  2. 一致性(Consistency):事务执行前后,数据库从一个一致状态转移到另一个一致状态。
  3. 隔离性(Isolation):支持多种事务隔离级别(READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE),默认为 REPEATABLE READ
  4. 持久性(Durability):一旦事务提交,其结果将永久保存,即使系统崩溃也不会丢失。
  1. InnoDB 使用行级锁而非表级锁,大大提高了并发性能。
  2. 支持共享锁(S 锁)和排他锁(X 锁),并结合意向锁(Intention Locks)实现高效的锁管理。
  3. 在大多数 SELECT 语句中使用 多版本并发控制(MVCC),避免读写冲突,提升并发效率。

1.3.外键约束(Foreign Key Constraints)

Section titled “1.3.外键约束(Foreign Key Constraints)”
  1. InnoDB 支持定义外键,确保引用完整性。
  2. 当插入、更新或删除涉及外键的记录时,InnoDB 自动检查约束条件,防止数据不一致。

1.4. 崩溃恢复机制(Crash Recovery)

Section titled “1.4. 崩溃恢复机制(Crash Recovery)”

InnoDB 使用 redo log(重做日志)undo log(回滚日志) 实现崩溃后的自动恢复。

  1. Redo Log:记录已提交事务的物理修改,用于恢复未写入磁盘的数据。
  2. Undo Log:用于回滚未提交事务,并支持 MVCC 的历史版本读取。
  1. MVCC 允许读操作不阻塞写操作,写操作也不阻塞读操作。
  2. 通过保存数据的多个版本(利用 undo log)实现快照读(snapshot read)。
  3. 在 REPEATABLE READ 隔离级别下,InnoDB 能有效防止“幻读”(通过 Next-Key Locking)。

1.6.自适应哈希索引(Adaptive Hash Index)

Section titled “1.6.自适应哈希索引(Adaptive Hash Index)”

InnoDB 的底层索引结构是 B+ 树,通常一次等值查询需要 O(log n) 的时间复杂度(需遍历树的高度)。而哈希索引支持 O(1) 的等值查找。

  1. InnoDB 在后台监控对 B+ 树索引 的访问模式。
  2. 如果发现某个索引的某些值被 频繁访问(例如,多次执行 WHERE id = 100),InnoDB 会自动在 缓冲池(Buffer Pool) 中为这些索引条目构建一个 哈希索引
  3. 后续对该值的查询可以直接通过哈希表定位,无需遍历 B+ 树。

InnoDB 的逻辑存储结构可以分为五层,从大到小依次为 表空间(Tablespace) → 段(Segment) → 区(Extent) → 页(Page) → 行(Row) 每一层都有其功能和组织方式,如下图所示:

Tablespace(表空间)
└── Segment(段)
├── Data Segment(数据段,即聚簇索引)
├── Index Segment(索引段,每个二级索引一个)
└── Rollback Segment(回滚段,用于 undo 日志)
└── Extent(区,1MB)
└── Page(页,16KB,默认)
└── Row(行记录)

InnoDB 存储数据的最高物理容器,对应一个或多个磁盘文件。

类型

  1. 系统表空间(System Tablespace)

    • 文件名:ibdata1, ibdata2, …

    • 存储:数据字典、双写缓冲(Doublewrite Buffer)、undo 日志(旧版本)、系统表等。

    • MySQL 5.7+ 默认不推荐将用户表放在此处。

  2. 独立表空间(File-Per-Table Tablespace)

    • 每张用户表对应一个 .ibd 文件(如 users.ibd)。

    • 由参数 innodb_file_per_table = ON 控制(MySQL 5.6+ 默认开启)。

    • 优点:便于表级备份、空间回收(TRUNCATE/DROP 可释放磁盘空间)。

  3. 通用表空间(General Tablespace)(MySQL 5.7+)

    • 可通过 CREATE TABLESPACE ts1 ADD DATAFILE 'ts1.ibd' 创建。

    • 支持多个表共享同一个表空间文件,适用于 SSD 优化或管理需求。

  4. 临时表空间(Temporary Tablespace)

    • 存储内部临时表和用户临时表(如 CREATE TEMPORARY TABLE)。

在 InnoDB 存储引擎中,**段(Segment)是表空间内的逻辑分配单元,用于组织和管理同类数据。

每张表至少包含两个主要段:叶子节点段(Leaf Node Segment)非叶子节点段(Non-Leaf Node Segment)。其中,叶子节点段负责存储聚簇索引的数据行,也就是表中的实际数据;而非叶子节点段则存储聚簇索引的内部节点,即 B+ 树的上层结构,用于维护索引的层级关系。

除此之外,每个二级索引也独立拥有自己的两个段,分别存储该索引的叶子节点与非叶子节点,从而保证索引结构的完整性与独立性。最后,系统还包含一个特殊的回滚段(Rollback Segment),它用于保存 undo 日志,以支持 MVCC(多版本并发控制)和事务回滚等核心功能,使得 InnoDB 能够在并发场景下保持数据的一致性与可恢复性。

区(Extent) 是 InnoDB 存储引擎中分配存储空间的基本单位,其大小固定为 1 MB,由 64 个连续的页(每页 16 KB) 组成。

每当表中的数据量增加时,InnoDB 会以“区”为单位向段(Segment)分配空间,以实现高效的空间管理和连续的数据存储。在表刚创建或数据量较小时,可能并不会立即占用完整的区,而是仅使用其中的部分页,这种情况下的区被称为碎片区混合区。随着数据的增长,这些混合区会逐渐演变为完整的区。

根据用途不同,区可分为多种类型:系统区用于存放系统内部信息,Undo 区用于存储回滚日志以支持事务与 MVCC,数据区用于保存表中的实际数据,索引区则专门用于存放索引结构,而空闲区则表示尚未被分配的空间。这样的设计既提高了空间利用率,也优化了数据访问的连续性和性能。

页(Page / Block) 是 InnoDB 存储引擎中最小的 I/O 单位,也是内存与磁盘之间交互的基本载体。

默认情况下,每个页的大小为 16 KB,但可以通过参数 innodb_page_size 调整为 4K、8K 或 16K。所有的数据读写操作都是以页为单位进行的,这意味着即使只修改一行数据,InnoDB 也会将整个页加载到内存中再进行更新。

根据功能不同,页可分为多种类型:数据页(B-Tree Node) 用于存储表中的实际数据,Undo 页 存放回滚日志以支持事务与多版本控制,系统页 保存元数据,索引页 用于记录索引结构,位图页 负责管理区(Extent)内各页的分配状态,而 空闲页 则表示尚未使用的存储空间。

一个典型的数据页内部结构包括文件头(File Header)、页头(Page Header)、虚拟记录(Infimum 与 Supremum)、用户记录区(User Records)、空闲空间(Free Space)、页目录(Page Directory)以及文件尾(File Trailer)等部分。这样的分层设计既保证了页内数据的有序性,又支持快速定位与高效的空间管理。在实际应用中,一个页通常可以容纳数百行数据,具体数量取决于表中字段的大小与存储格式。

┌──────────────────────────┐
│ File Header (38字节) │ ← 文件头信息
│ Page Header (56字节) │ ← 页头信息
│ Infimum + Supremum │ ← 虚拟最小/最大记录
│ User Records │ ← 实际存储的记录
│ Free Space │ ← 空闲空间
│ Page Directory │ ← 记录偏移地址表
│ File Trailer (8字节) │ ← 校验信息
└──────────────────────────┘

行(Row) 是 InnoDB 存储的最小逻辑单位,对应数据库表中的一条记录。每一行记录由多个部分组成,包括记录头信息(Record Header)实际数据字段(Columns) 以及若干隐藏列(Hidden Columns)

记录头信息中通常包含事务 ID、回滚指针等元数据,用于支持事务一致性与并发控制;数据字段则存储用户定义的列值;而隐藏列是 InnoDB 自动维护的内部字段,其中 DB_TRX_ID 记录最后一次修改该行的事务 ID,DB_ROLL_PTR 指向相应的 Undo 日志位置,以便在事务回滚或快照读取时恢复旧版本数据,DB_ROW_ID 则是在表没有主键时由系统自动生成的隐藏主键,用于唯一标识该行。

当某一行的数据过大(例如包含 TEXTBLOB 类型字段)导致超出单页容量时,就会出现 行溢出(Row Overflow) 现象。此时,InnoDB 会将超出的部分内容存放在独立的 溢出页(Overflow Page) 中,并在原页中保留指向这些溢出页的指针。通过这种机制,InnoDB 能够高效地存储和访问大字段数据,同时保持行记录结构的完整与灵活性。