2. 数据库索引基础:B+树索引结构、聚簇索引与非聚簇索引、索引的优缺点
在高并发评分通道场景下,索引是 SQL 性能优化的第一道防线。理解索引的底层结构(B+树)和存储方式(聚簇 vs 非聚簇),是后续所有优化技巧的基石。本节从数据结构出发,逐步拆解索引的核心原理与取舍。
2.1 B+树索引结构
B+树是关系型数据库(如 MySQL InnoDB、PostgreSQL)默认的索引实现。它是一种平衡多路查找树,具有以下关键特征:
- 非叶子节点只存储键值(索引列值)和指向子节点的指针,不存储实际数据行。
- 叶子节点存储完整的索引键值以及指向数据行的指针(或聚簇索引中的主键值),并且叶子节点之间通过双向链表连接,形成有序序列。
- 所有数据行都存储在叶子节点层,查询任何一条记录都需要从根节点走到叶子节点,路径长度相同(树高稳定)。
B+树相比红黑树或二叉搜索树的优势在于:树高更低(通常 3~4 层),磁盘 I/O 次数少,非常适合磁盘块存储的数据库系统。
高并发启示: B+树的“矮胖”结构保证了每次查询的 I/O 次数恒定,这是索引在高并发下仍能保持低延迟的核心原因。树高每增加一层,查询耗时可能增加一个数量级。
2.2 聚簇索引与非聚簇索引
根据数据行在磁盘上的物理存储顺序与索引键顺序的关系,索引分为两种类型:
| 对比维度 | 聚簇索引(Clustered Index) | 非聚簇索引(Non-Clustered Index) |
|---|---|---|
| 数据存储方式 | 数据行物理存储在索引的叶子节点上,索引即数据 | 叶子节点存储索引键值 + 指向数据行的指针(或聚簇索引键) |
| 每张表数量 | 只能有 1 个(数据只能按一种顺序物理排列) | 可以有多个(通常建议不超过 5~6 个) |
| 查询速度 | 通过聚簇索引查询非常快(直接定位数据行) | 查询后可能需要“回表”查询聚簇索引(二次查找) |
| 插入/更新开销 | 插入可能导致页分裂,开销较大 | 插入时只维护索引树,开销相对较小 |
| 典型实现 | MySQL InnoDB 的主键索引 | MySQL InnoDB 的二级索引(辅助索引) |
高并发评分通道实战: 在评分结果表(如
score_result)中,通常使用自增主键作为聚簇索引。自增主键能保证新插入的数据行在物理上连续追加,避免频繁的页分裂,从而提升高并发写入性能。
2.3 索引的优缺点
索引不是越多越好。在高并发系统中,索引的代价必须被精确评估。
优点
- 加速查询: 将全表扫描(O(n))降为 B+树查找(O(log n)),对于大表效果显著。
- 保证唯一性: 唯一索引可以防止重复数据写入。
- 减少排序和分组开销: B+树叶子节点天然有序,
ORDER BY和GROUP BY可以直接利用索引顺序,避免文件排序(filesort)。 - 覆盖索引: 如果查询列全部包含在索引中,可以避免回表,进一步提升性能。
缺点
- 写入性能下降: 每次 INSERT、UPDATE、DELETE 都需要同步维护索引树,索引越多,写入越慢。
- 占用额外磁盘空间: 每个索引都是一棵 B+树,会占用大量存储。
- 维护成本高: 索引碎片化、统计信息过期等问题需要定期维护(如
OPTIMIZE TABLE)。 - 可能导致查询计划错误: 优化器可能选择错误的索引,导致性能反而下降。
高并发避坑: 非聚簇索引的“回表”操作在高并发场景下可能成为性能瓶颈。如果查询频繁且回表次数多,建议使用覆盖索引(将查询列全部包含在索引中)来消除回表,或者直接使用聚簇索引查询。
2.4 索引选择原则(高并发视角)
- 主键选择自增整数: 避免 UUID 或随机字符串作为聚簇索引,防止频繁页分裂。
- 区分度高的列优先: 索引列的区分度(Cardinality)越高,过滤效果越好。
- 短索引优先: 索引列长度越短,B+树节点能存储更多键值,树高更低。
- 避免冗余索引: 联合索引 (a, b) 可以覆盖 (a) 的查询,不需要再单独建 (a) 索引。
- 控制索引数量: 单表索引建议不超过 5~6 个,写入密集型表建议更少。
评分通道案例: 在评分明细表
score_detail 中,查询模式通常是 WHERE user_id = ? AND score_date BETWEEN ? AND ?。建议建立联合索引 (user_id, score_date),利用 B+树的有序性同时加速等值查询和范围查询,避免文件排序。