4、索引原理与B+树结构
索引是数据库性能调优的核心武器。要理解为什么索引能加速查询,必须先深入其底层数据结构——B+树。本节将拆解B+树的结构、聚簇与非聚簇索引的差异、索引选择性以及复合索引的最左前缀原则,帮助你从原理层面掌握索引设计。
4.1 B+树数据结构
B+树是一种多路平衡查找树,是MySQL InnoDB存储引擎默认的索引结构。与二叉树或B树不同,B+树将所有数据存储在叶子节点,非叶子节点只存储键值和指针,不存储实际数据。这种设计带来了两个关键优势:
- 树高更低:每个节点可存储大量键值(通常一个节点对应一个磁盘页,约16KB),使得三层B+树即可支撑千万级数据,查询只需3~4次磁盘I/O。
- 范围查询高效:叶子节点通过双向链表连接,支持顺序扫描和范围查找,无需回根节点重新遍历。
关键指标:InnoDB中B+树每个节点默认16KB。假设主键为BIGINT(8字节)+ 指针(6字节),每个节点可存储约1170个键值对。三层B+树可存储约 1170 × 1170 × 16 = 2190万条记录。
4.2 聚簇索引与非聚簇索引
InnoDB中,索引分为聚簇索引和非聚簇索引(也称二级索引)。它们的核心区别在于数据存储位置和索引结构。
| 对比维度 | 聚簇索引 | 非聚簇索引(二级索引) |
|---|---|---|
| 数据存储 | 叶子节点直接存储完整数据行 | 叶子节点存储主键值(或行指针) |
| 数量限制 | 每张表只能有一个 | 每张表可以有多个 |
| 默认主键 | InnoDB自动以主键创建聚簇索引 | 用户手动创建的普通索引 |
| 查询回表 | 直接返回数据,无需回表 | 需要根据主键回聚簇索引查询完整数据 |
| 范围查询 | 高效,叶子节点有序且连续 | 相对低效,需多次回表 |
实践建议:在InnoDB中,建议为每张表显式定义自增主键。自增主键能保证新插入的数据在B+树末尾追加,减少页分裂和碎片。如果使用UUID作为主键,随机写入会导致频繁的页分裂,严重影响写入性能。
4.3 索引选择性
索引选择性是指索引列中不同值的数量与总行数的比值。选择性越高,索引的过滤效果越好,查询效率越高。
- 计算公式:选择性 = COUNT(DISTINCT column) / COUNT(*)
- 理想值:接近1(如主键、唯一键)
- 低选择性示例:性别列(只有男/女),选择性约0.5,索引效果差
- 高选择性示例:用户邮箱、身份证号,选择性接近1
避坑提醒:不要对低选择性的列单独建索引。例如在性别列上建索引,查询时可能仍需要扫描大量数据,甚至被优化器放弃使用索引。低选择性列更适合作为复合索引的后缀列,而非前缀列。
4.4 复合索引最左前缀原则
复合索引(多列联合索引)遵循最左前缀原则:查询条件必须从索引的最左列开始,并且不能跳过中间的列,否则索引无法被完全使用。
假设在 (a, b, c) 三列上创建复合索引,索引的B+树会先按a排序,a相同再按b排序,b相同再按c排序。以下查询能使用索引的情况:
| 查询条件 | 是否使用索引 | 说明 |
|---|---|---|
WHERE a = 1 |
✅ 完全使用 | 从最左列开始 |
WHERE a = 1 AND b = 2 |
✅ 完全使用 | 连续使用前两列 |
WHERE a = 1 AND b = 2 AND c = 3 |
✅ 完全使用 | 全部命中 |
WHERE b = 2 |
❌ 无法使用 | 跳过了最左列a |
WHERE a = 1 AND c = 3 |
⚠️ 部分使用 | 只能用到a列,c列无法利用索引排序 |
WHERE a > 1 AND b = 2 |
⚠️ 部分使用 | a列范围查询后,b列无法继续使用索引 |
4.5 索引设计实战要点
- 优先高选择性列:将选择性高的列放在复合索引的最左侧,最大化过滤效果。
- 避免冗余索引:如果已有
(a, b)索引,单独建(a)索引是冗余的,因为最左前缀已覆盖。 - 覆盖索引优化:如果查询的所有列都在索引中,则无需回表,称为“覆盖索引”。例如
SELECT a, b FROM t WHERE a = 1且索引为(a, b)。 - 排序与索引:
ORDER BY也能利用索引排序,但必须遵循最左前缀。例如索引(a, b)可以优化ORDER BY a, b,但无法优化ORDER BY b, a。
Go Web服务实战提示:在Go的ORM框架(如GORM、SQLx)中,通过
EXPLAIN 分析查询计划时,重点关注 type 字段(const/ref/range 为优,ALL 为全表扫描)和 Extra 字段(Using index 表示覆盖索引,Using filesort 表示需要额外排序)。结合本节B+树原理,可以快速定位索引失效原因。