索引设计原则:高选择性列优先、避免过多索引、覆盖索引优化

索引这东西,说白了就是数据库的「目录」。但目录怎么建,建多少,学问很大。我见过太多人一上来就给所有列都加上索引,结果查询没快多少,写入反而慢得离谱。今天咱们就聊聊索引设计的三个核心原则。

核心三原则:高选择性列优先、避免过多索引、覆盖索引优化。这三条你吃透了,80% 的索引问题都能搞定。

3.1 高选择性列优先

什么叫选择性?简单说就是这一列里不同值的比例。比如性别列只有「男」「女」两种值,选择性就很低;而身份证号几乎每条记录都不同,选择性就很高。

我个人习惯,选择性低于 20% 的列,基本不考虑单独建索引。为什么?你想想看,如果索引帮你过滤掉一半数据,那跟全表扫描有啥区别?

经验公式:选择性 = COUNT(DISTINCT column) / COUNT(*)。值越接近 1,索引效果越好。

我在项目中遇到过这样一个案例:有个订单表,status 列只有 3 种状态值,但数据量有 500 万行。开发同学给 status 建了索引,结果查询还是很慢。我一看执行计划,好家伙,索引扫描回了 200 万行数据。这就是典型的选择性太低导致的。

那什么时候低选择性列也有用?组合索引的前缀列。比如 (status, create_time) 这种,status 虽然选择性低,但能快速定位到某个状态的数据范围,再配合 create_time 做排序或范围过滤,效果就出来了。

选择性范围 推荐策略 举例
> 80% 非常适合建索引 用户ID、订单号、身份证
20% - 80% 视查询频率而定 城市、分类ID
< 20% 不建议单独建索引 性别、状态、是否删除

3.2 避免过多索引

很多新手有个误区:索引越多查询越快。其实不是这样的。每个索引都是一棵 B+ 树,插入、更新、删除时都要维护这棵树。索引多了,写入性能直线下降。

我记得有一次帮一个电商系统做优化,他们的商品表有 12 个索引。每次上架一个新商品,插入耗时超过 500 毫秒。我删掉了 5 个冗余索引后,插入时间降到了 80 毫秒。查询性能反而没受影响,因为那些索引根本没用上。

避坑指南:我曾经见过一张表有 20 多个索引,DBA 还觉得「多多益善」。结果每次大促期间写入延迟飙升,最后不得不紧急删索引。记住:索引不是越多越好,够用就行。

那怎么判断索引是不是多了?我一般看三个指标:

  • 索引使用率:通过 sys.schema_unused_indexes 或 pg_stat_user_indexes 查看哪些索引从来没被用过
  • 索引冗余度:比如 (a, b) 和 (a) 这两个索引,后者就是冗余的,因为前者已经能覆盖
  • 写入延迟:如果写入慢但查询快,先检查索引数量

一般建议,单表索引数量控制在 5 个以内。当然这不是硬性规定,但超过 8 个你就要警惕了。

3.3 覆盖索引优化

覆盖索引是性能优化的利器。什么叫覆盖索引?就是索引里已经包含了查询需要的所有列,不需要回表查数据行。

举个例子:

-- 假设有索引 (user_id, status, create_time)
-- 这个查询就不需要回表
SELECT user_id, status, create_time 
FROM orders 
WHERE user_id = 12345 
  AND status = 'paid';

为什么覆盖索引快?因为 B+ 树的叶子节点只存索引列和主键,数据量比整行小得多。一次索引扫描就能拿到所有数据,省掉了回表的随机 I/O。

我在项目中遇到过这样一个场景:有个报表查询要查 10 万条数据,每次跑要 3 秒多。我看了下查询语句,只用了 4 个字段。于是建了一个覆盖索引把这 4 个字段都包含进去,查询时间直接降到 0.3 秒。10 倍的提升,就改了一个索引。

小技巧:MySQL 里可以用 EXPLAIN 查看执行计划,如果 Extra 列显示 Using index,说明用到了覆盖索引。如果显示 Using index condition,那是索引条件下推,比覆盖索引差一点。

但覆盖索引也不是无脑加。要注意几点:

  • 不要把所有列都塞进索引,索引列越多,维护成本越高
  • 优先覆盖高频查询的字段,特别是 WHERE、JOIN、ORDER BY 里的列
  • 如果查询需要返回大量列,覆盖索引可能得不偿失

下面这张图总结了三个原则的关系:

索引设计三原则 索引设计核心 原则一:高选择性列优先 选择性 > 20% 才考虑建索引 原则二:避免过多索引 单表建议不超过 5 个索引 原则三:覆盖索引优化 索引包含查询所需全部字段 组合索引前缀列可放宽 定期检查未使用索引 EXPLAIN 看 Using index 三个原则要一起用 先选高选择性列做主键 控制索引数量,用覆盖索引减少回表

最后说一句,索引设计不是一劳永逸的事。业务在变,查询模式也在变。我建议每季度做一次索引健康检查,看看哪些索引该加、哪些该删。嗯,今天就聊到这儿,记住这三个原则,你的索引设计就不会跑偏。