06.索引设计原则与代价权衡
文章目录
问题: 在设计索引时,如何利用区分度(Cardinality)和选择性(Selectivity)选择最优列?在高频写入(INSERT/UPDATE/DELETE)场景下,索引过多会带来哪些维护代价,如何权衡读写性能?
👇 点击展开/折叠 面试背诵模板(建议先背这个)
一、索引列选择:区分度与选择性
Cardinality(基数):索引列中不重复值的估算数量,通过 SHOW INDEX FROM table 查看。Cardinality 越高,说明该列值越分散,索引过滤效果越好。
Selectivity(选择性):选择性 = Cardinality / 总行数,取值在 (0, 1] 之间。值越接近1,该列作为索引列的效果越好。例如:
- 性别列:选择性 ≈ 0.5(男女各半),过滤效果差。
- 身份证号列:选择性 ≈ 1(几乎全唯一),过滤效果极佳。
实践建议:
- 组合索引中,选择性高的列放在左侧,优先过滤掉最多数据。
- 若列的选择性 < 0.1(即重复值超过90%),该列单独建索引收益极低,不建议作为独立索引。
- 对长字符串字段(如 VARCHAR(255)),可考虑前缀索引(
CREATE INDEX idx_col ON table(col(10))),牺牲部分区分度换取索引体积缩减。
区分度的统计信息优化:Cardinality 是统计信息,默认通过采样计算(InnoDB 对 N 个页随机采样)。若统计信息不准确,可执行 ANALYZE TABLE 重新计算,帮助优化器选择更优执行计划。
二、索引维护代价(高频写入场景)
INSERT 代价:
- 每插入一行,需要向该表的所有索引(聚簇索引 + 所有二级索引)分别插入索引条目。
- 若索引列值无序(如 UUID),B+树页分裂频繁,导致磁盘随机写入和碎片化,吞吐量下降。
UPDATE 代价:
- 若更新的列未建索引,更新代价较低(仅修改聚簇索引数据页)。
- 若更新涉及索引列,需先删除旧索引条目(逻辑删除 + 标记),再插入新条目,额外产生两个操作。
- 若更新主键列,代价极高(主键变更导致整行数据在聚簇索引中移动,且所有二级索引的主键指针需同步更新)。
DELETE 代价:
- 从聚簇索引中删除行,同时需要从所有二级索引中删除对应的索引条目(标记删除 + 页合并)。
- 大量删除会产生碎片(页空洞),影响后续插入性能(需执行
OPTIMIZE TABLE重建表整理空间)。
索引数量对写入性能的影响:
| 索引数量 | 写入性能影响 | 典型场景 |
|---|---|---|
| 0 ~ 3 个 | 写入性能影响小 | 日志表、流水表(只读或写多读少) |
| 4 ~ 6 个 | 写入性能有一定损耗,可接受 | 普通业务表(读多写少) |
| > 6 个 | 写入性能显著下降,需评估是否必要 | 过度索引的典型信号 |
三、读写性能权衡策略
决策矩阵:
| 业务特征 | 索引策略 | 理由 |
|---|---|---|
| 读多写少(如报表、详情页) | 可多建索引(≤6个),包含覆盖索引 | 查询性能优先,写入频率低可容忍维护开销 |
| 写多读少(如日志、流水) | 仅保留主键索引,尽量避免二级索引 | 写入吞吐量优先,查询可离线或通过其他方式处理 |
| 读写均衡(如订单、用户表) | 针对高频查询建3~5个索引,定期删除未使用索引 | 平衡读写性能,动态监控调整 |
索引瘦身策略:
- 索引下推/覆盖索引优先:用覆盖索引替代多个单列索引,减少索引数量同时提升查询效率。
- 删除重复索引:如
(a, b)和(a)重复,保留前者(包含后者功能)。 - 定期审查未使用索引:通过
performance_schema或pt-index-usage工具,删除长时间未被使用的索引。 - 分库分表/归档:对历史数据做冷热分离,热表轻量化索引,冷表全量索引用于离线分析。
四、核心总结
- 优先选择选择性高的列建索引,通过
Cardinality / total_rows评估过滤效果。 - 索引数量与写入性能成反比,控制单表索引数量 ≤ 5~6 个是通用准则。
- 权衡本质是成本效益分析:以“查询频率 × 查询性能提升”与“写入频率 × 维护开销”做量化对比(不需要精确计算,但需有明确的取舍依据)。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 LGQ的博客!