2. 数据库索引基础:B+树索引结构、聚簇索引与非聚簇索引、索引的优缺点

在高并发评分通道场景下,索引是 SQL 性能优化的第一道防线。理解索引的底层结构(B+树)和存储方式(聚簇 vs 非聚簇),是后续所有优化技巧的基石。本节从数据结构出发,逐步拆解索引的核心原理与取舍。

2.1 B+树索引结构

B+树是关系型数据库(如 MySQL InnoDB、PostgreSQL)默认的索引实现。它是一种平衡多路查找树,具有以下关键特征:

  • 非叶子节点只存储键值(索引列值)和指向子节点的指针,不存储实际数据行。
  • 叶子节点存储完整的索引键值以及指向数据行的指针(或聚簇索引中的主键值),并且叶子节点之间通过双向链表连接,形成有序序列。
  • 所有数据行都存储在叶子节点层,查询任何一条记录都需要从根节点走到叶子节点,路径长度相同(树高稳定)。

B+树相比红黑树或二叉搜索树的优势在于:树高更低(通常 3~4 层),磁盘 I/O 次数少,非常适合磁盘块存储的数据库系统。

高并发启示: B+树的“矮胖”结构保证了每次查询的 I/O 次数恒定,这是索引在高并发下仍能保持低延迟的核心原因。树高每增加一层,查询耗时可能增加一个数量级。
B+树索引结构示意图 根节点 [50, 120] 中间节点 [20, 35] 中间节点 [80, 150] 叶子节点 键:10,15,20 叶子节点 键:25,30,35 叶子节点 键:40,50,55 叶子节点 键:60,80,90 叶子节点 键:120,150,200 图例说明: 蓝色节点 = 非叶子节点(仅存储键值和指针,不存数据) 绿色节点 = 叶子节点(存储完整键值和数据指针/主键,红色箭头表示双向链表) 查询路径:根节点 → 中间节点 → 叶子节点(所有数据都在叶子层,树高稳定)

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)。
  • 可能导致查询计划错误: 优化器可能选择错误的索引,导致性能反而下降。
聚簇索引与非聚簇索引查询流程对比 聚簇索引查询(主键查找) B+树根节点 → 中间节点 叶子节点(直接包含数据行) 直接返回数据行(1次I/O路径) 非聚簇索引查询(二级索引查找) B+树根节点 → 中间节点 叶子节点(存储索引键 + 主键值) 回表:根据主键到聚簇索引再次查找 返回数据行(2次I/O路径)
高并发避坑: 非聚簇索引的“回表”操作在高并发场景下可能成为性能瓶颈。如果查询频繁且回表次数多,建议使用覆盖索引(将查询列全部包含在索引中)来消除回表,或者直接使用聚簇索引查询。

2.4 索引选择原则(高并发视角)

  1. 主键选择自增整数: 避免 UUID 或随机字符串作为聚簇索引,防止频繁页分裂。
  2. 区分度高的列优先: 索引列的区分度(Cardinality)越高,过滤效果越好。
  3. 短索引优先: 索引列长度越短,B+树节点能存储更多键值,树高更低。
  4. 避免冗余索引: 联合索引 (a, b) 可以覆盖 (a) 的查询,不需要再单独建 (a) 索引。
  5. 控制索引数量: 单表索引建议不超过 5~6 个,写入密集型表建议更少。
评分通道案例: 在评分明细表 score_detail 中,查询模式通常是 WHERE user_id = ? AND score_date BETWEEN ? AND ?。建议建立联合索引 (user_id, score_date),利用 B+树的有序性同时加速等值查询和范围查询,避免文件排序。