InnoDB 的每一个索引(主键索引、二级索引)本质上都是一棵 独立的 B+Tree。
区别在于:
例如:
CREATE INDEX idx_name ON t(name);
InnoDB 构建的 B+Tree:
key: name
value: 主键ID(主键索引的值)
叶子节点存储:
(name, pk)
特点:
- key 比较简单
- 排序方式基于一个列
- 体积相对更小
例如:
CREATE INDEX idx_user ON t(name, age);
InnoDB 构建的 B+Tree:
key: (name, age)
value: 主键ID
叶子节点存储:
(name, age, pk)
关键点:
- 索引 key 是“按列顺序拼接”后的二进制序列
- 排序规则:先按 name 排序,相同 name 再按 age 排序
- 比单列索引占用更多空间
- 能提高多条件查询效率(最左前缀原则)
例如联合索引:
(name, age, create_time)
B+Tree 的排序顺序为:
(name asc, age asc, create_time asc)
这是 InnoDB 固定行为,无法修改顺序。
所有 二级索引 的叶子节点都存放:
- key(单列 key 或多列 key)
- 主键值(PK)
(name, pk)
(name, age, pk)
加入字段越多,叶子节点变大 → 一个 page 能放的记录数变少 → B+Tree 更深 → 性能更差。
面试官会重点问这一点:
为什么联合索引比单列索引更占空间?
因为叶子节点包含更多列。
单列索引: 只能利用一个字段 查询:
WHERE name = 'A' AND age = 20
单列索引 idx_name 会:
- 找到 name='A' 的 pk 列表
- 再回表取 age
- 然后过滤 age=20
成本较高。
联合索引: 已经包含 name + age 不需要回表过滤第二个字段,减少大量 I/O。
这就是为什么:
(name, age)
比单列两个索引 (name) 和 (age) 更高效。
联合索引 (name, age, city) 的 B+Tree 实际结构:
key1: (name, age, city)
leaf: (name, age, city, pk)
排序方式类似:
(name1, age1, city1)
(name1, age1, city2)
(name1, age2, city1)
(name2, age1, city1)
因此可以连续使用的列必须从最左开始匹配。
因为 tree 是按 name 排序的,age 分布是乱序的。
存储结构决定了:
(name, age) 可以走索引
(age) 不能走索引
最左前缀原则来自 B+Tree key 的排序机制,不是 MySQL 的“语法限制”。
| 项目 | 单列索引 | 联合索引 |
|---|---|---|
| B+Tree 数量 | 1 树 | 1 树 |
| key 结构 | 一个列 | 多列拼接形成排序 key |
| 叶子节点 | (col, pk) | (col1, col2, ..., pk) |
| 空间消耗 | 小 | 大 |
| 能否覆盖 where 多字段 | No | Yes(最左前缀) |
| 回表 | 多 | 少 |
| 适用场景 | 单字段过滤 | 组合查询、排序加速、覆盖索引 |
InnoDB 的单列索引和联合索引本质上都是独立的 B+Tree,只是存储的 key 不同。
单列索引的 key 是一个列;联合索引的 key 是多个列按顺序拼接形成的复合排序键。
因为联合索引的叶子节点包含更多列,所以占空间更大,但可以减少回表并加速复合查询。
最左前缀原则本质上是联合索引的物理排序结构决定的。