Skip to content

InnoDB 引擎与索引事务:B+ 树、聚簇索引与隔离级别

基于 MySQL 8.4 LTS / 9.x · 核于 2026-08

速查

  • 存储引擎分层:server 层(连接/解析/优化/执行,跨引擎)+ InnoDB 引擎层(行锁/MVCC/崩溃恢复/外键,真正存数据)。ENGINE=InnoDB 选引擎,95% 场景用它。
  • InnoDB 四大支柱:①行锁 + MVCC(读写不冲突);②崩溃恢复(redo log 持久化、undo log 回滚,WAL 先写日志);③外键约束(跨表引用完整);④聚簇索引(主键索引与数据一体)。
  • B+ 树索引:数据只在叶子节点(非叶子节点只路由),叶子用双向链表串成有序。扇出大→3-4 层存亿级行;链表→范围查询顺着扫。
  • 聚簇索引 vs 二级索引:聚簇索引(主键)叶子存整行;二级索引叶子存主键值,查二级索引后要回表(去聚簇索引取整行)。覆盖索引(索引含查询全部列)避免回表,EXPLAINUsing index
  • 联合索引最左前缀INDEX(a,b,c) 支持 a / a,b / a,b,c,不支持跳过 ab / c。顺序按「区分度高、查询频繁、常作范围」排。
  • 索引下推 ICP(5.6+):把 WHERE 对二级索引列的过滤下推到引擎层,减少回表次数。
  • ACID:原子性(undo log 回滚)、持久性(redo log 刷盘)、隔离性(锁+MVCC)、一致性(AID 共同保证)。
  • 四种隔离级别:READ UNCOMMITTED < READ COMMITTED(PG 默认)< REPEATABLE READ(MySQL 默认)< SERIALIZABLE。
  • MySQL 的 RR 用间隙锁:RR 下用**临键锁(next-key lock = 行锁 + 间隙锁)**避免幻读,比 Oracle/PG 在 RR 下仍有幻读更严格。
  • MVCC:每行有 undo log 版本链,事务开始生成一致性视图,读不加锁看快照——读写不冲突。代价:长事务→undo 膨胀→purge 跟不上→空间不回收。
  • 锁体系:共享/排他锁(S/X);行锁分记录锁(锁单行)、间隙锁(锁区间,防插入)、临键锁(记录+前区间);意向锁(IS/IX,表级,标「有行锁」加快冲突判断)。
  • 死锁:MySQL 自动检测并回滚代价小的事务。预防:固定加锁顺序、小事务、FOR UPDATE 谨慎。

一、InnoDB 存储结构:表空间、段、区、页

InnoDB 把数据组织成层次结构,理解它能解释很多调优现象:

表空间(ibd 文件)
  └ 段(segment):索引段 + 数据段 + 回滚段
     └ 区(extent):1MB = 64 个页
        └ 页(page):16KB(默认),磁盘 IO 的最小单位
           └ 行(row):每行有事务 ID + 回滚指针 + 数据
  • **页(page)**是 InnoDB 磁盘 IO 的最小单位——一次读写一页(16KB),不是一行。所以 innodb_buffer_pool 也以页为单位缓存。
  • 行内有隐藏列:每行除用户数据外,还有 DB_TRX_ID(最后修改它的事务 ID)、DB_ROLL_PTR(指向 undo log 的回滚指针)——这是 MVCC 的基础。
  • innodb_buffer_pool_size:缓存热页的内存区,最重要的参数,设可用内存的 50-70%。命中率应 > 99%。

二、B+ 树索引:为什么不是 B 树或红黑树

InnoDB 索引统一用 B+ 树(B-plus tree),关键区别:

结构数据存哪范围查询树高适用
B 树所有节点都存数据需中序遍历,慢文件系统
B+ 树只在叶子,叶子双向链表顺着链表扫,极快(扇出大)数据库索引
红黑树每节点一值不适合范围内存结构(C++ map)
  • 为什么 B+ 树:①扇出大——非叶子节点不存数据只存键,一个 16KB 页能存几百个键,3-4 层就能索引亿级行(每次查 3-4 次磁盘 IO);②范围查询快——叶子节点链表,WHERE id BETWEEN 100 AND 200 找到 100 后顺着链表扫到 200 即可。
  • 哈希索引不是默认:InnoDB 有「自适应哈希索引」(AHI),对热点查询自动建内存哈希,但不可手动控制。需要精确点查的内存场景用 Memory 引擎或 Redis。

三、聚簇索引与二级索引:回表与覆盖索引

InnoDB 的索引按「叶子存什么」分两类:

聚簇索引(主键索引):
   [非叶子: 主键路由]

   [叶子: 主键值 + 整行数据]   ← 数据就存在主键索引的叶子里

二级索引(如 INDEX(name)):
   [非叶子: name 路由]

   [叶子: name 值 + 主键值]   ← 只存主键,不存整行
  • 聚簇索引只有主键:一张 InnoDB 表只有一个聚簇索引(主键)。没有显式主键时,InnoDB 选第一个 NOT NULL 唯一索引;都没有就生成隐藏 6 字节 ROW_ID(不推荐,不可控)。
  • 回表(lookup):用二级索引查到主键值后,还要去聚簇索引取整行——多一次 B+ 树查找。如果查询只需索引列与主键,则无需回表,称为覆盖索引
  • 覆盖索引:让索引覆盖查询所需全部列,EXPLAINExtra 显示 Using index。例如 SELECT id, name FROM user WHERE name=?,若 name 是二级索引,id(主键)已在叶子,覆盖索引成立,避免回表。
  • 主键设计建议:用自增整型BIGINT AUTO_INCREMENT)——顺序插入,叶子节点追加,页分裂少;避免 UUID 主键(随机插入导致频繁页分裂、索引膨胀)。

四、联合索引与最左前缀

INDEX(a, b, c) 是一颗 B+ 树,按 abc 排序。最左前缀原则:只能从最左列开始连续匹配:

sql
-- 能用上 INDEX(a, b, c)
WHERE a = 1                       -- 用 a
WHERE a = 1 AND b = 2             -- 用 a, b
WHERE a = 1 AND b = 2 AND c = 3   -- 用 a, b, c
WHERE a = 1 AND c = 3             -- 只用 a(c 跳过了 b,无法用)

-- 用不上 INDEX(a, b, c)
WHERE b = 2                       -- 跳过了 a
WHERE b = 2 AND c = 3             -- 跳过了 a
  • 列顺序原则:①区分度高的放前面(过滤掉更多行);②查询最频繁的放前面;③范围查询列放最后(范围后的列用不上索引)。
  • 索引下推(ICP,5.6+)WHERE name LIKE '张%' AND age > 18,若 INDEX(name, age),引擎层先用 name 过滤再下推 age 过滤,减少回表。

五、ACID 与四种隔离级别

事务的 ACID 是一致性的保证:

性质含义实现
原子性(Atomicity)全做或全不做undo log(回滚未提交事务)
一致性(Consistency)合法状态→合法状态AID 共同保证 + 约束(外键/唯一/检查)
隔离性(Isolation)并发事务互不干扰锁(写)+ MVCC(读)
持久性(Durability)提交后断电不丢redo log(WAL,先写日志再改数据页)

并发事务会带来三类问题:脏读(读到别的事务未提交的数据)、不可重复读(同一事务两次读同一行结果不同)、幻读(同一事务两次范围查询结果集不同)。

四种隔离级别对应能避免哪些问题:

隔离级别脏读不可重复读幻读
READ UNCOMMITTED可能可能可能
READ COMMITTED避免可能可能
REPEATABLE READ(MySQL 默认)避免避免避免(间隙锁)
SERIALIZABLE避免避免避免
  • MySQL 默认 REPEATABLE READ——比 Oracle/PostgreSQL 默认的 READ COMMITTED 更严格。MySQL 在 RR 下用间隙锁/临键锁避免幻读,其他数据库在 RR 下仍可能有幻读。
  • 生产建议:多数业务用默认 RR 即可;对延迟敏感且能接受不可重复读的,可降到 RC(减少间隙锁、提升并发)。

六、MVCC:多版本并发控制

MVCC 让「读不阻塞写、写不阻塞读」,是高并发基础:

  • 版本链:每行的 DB_ROLL_PTR 指向 undo log 里的旧版本,形成「当前版本 → 旧版本 → 更旧版本」链表。
  • 一致性视图(read view):事务执行 SELECT 时生成一个视图,记录「当时活跃(未提交)的事务 ID 列表」。读操作根据视图判断「该看哪个版本」——只看视图生成时已提交的版本。
  • RC vs RR 的 MVCC 差异RC 每次 SELECT 都生成新视图(所以能看到别的事务新提交的,导致不可重复读);RR 事务第一次 SELECT 时生成视图并复用整个事务(所以可重复读)。
  • 代价——长事务是杀手:事务不结束,它用到的 undo 版本就不能被 purge(清理)线程回收 → undo 表空间膨胀 → 磁盘占用涨、历史版本链变长、查询变慢。生产务必避免长事务information_schema.innodb_trx 查长事务)。

七、锁:行锁、间隙锁与临键锁

InnoDB 的锁体系(RR 下):

锁类型锁什么用途
记录锁(Record Lock)锁单行命中行的写
间隙锁(Gap Lock)锁两行之间的区间(不含端点)防止区间内插入新行(防幻读)
临键锁(Next-Key Lock)记录 + 它前面的间隙RR 默认,记录锁 + 间隙锁的组合
意向锁(IS/IX)表级标记「表内有行锁」,加快表锁冲突判断
  • 共享锁(S)/排他锁(X):读加 S(LOCK IN SHARE MODE),写加 X(FOR UPDATE)。S 兼容 S,X 与任何锁互斥。
  • 死锁:事务 A 锁了行 1 等行 2,事务 B 锁了行 2 等行 1。InnoDB 自动检测死锁并回滚代价较小的事务(undo 量少的)。预防:①固定加锁顺序;②事务尽量短;③FOR UPDATE 谨慎用。

下一步

InnoDB 与索引事务讲透后,下一个核心是复制、JSON 与性能调优——binlog 三种格式与 GTID、半同步/组复制、JSON 类型与函数、连接池、EXPLAIN 实战与慢查询分析。