# 在什么情况下,不推荐为数据库建立索引?

6 min read
Table of Contents

在什么情况下,不推荐为数据库建立索引?

【核心定义】
不推荐为数据库建立索引的核心场景是:当索引带来的查询性能提升无法抵消其维护成本(写入开销、存储占用)时,应避免盲目创建。

【关键要点】

  1. 数据量极小的表

    • 结论:数据行数少(如 < 1000 行)时,全表扫描成本可能低于索引扫描。
    • 原理:索引查询需两次 I/O(索引树 + 数据行),而小表全表扫描可能一次加载到内存,且避免回表开销。
  2. 写多读少的表

    • 结论:高频写入(INSERT/UPDATE/DELETE)场景下,索引维护成本过高。
    • 原理:每次数据修改需同步更新索引树(B+Tree 分裂/合并),降低写入吞吐,增加锁竞争风险。
  3. 低区分度的列

    • 结论:列值重复率高(如性别、状态标志)时,索引过滤效果差。
    • 原理:B+Tree 索引通过区分度筛选数据,低区分度导致大量回表查询,可能劣于全表扫描。
  4. 频繁更新的字段

    • 结论:作为索引的字段被频繁修改,会触发索引节点重组。
    • 原理:InnoDB 中索引存储物理地址,字段更新可能引起行移动(页分裂),加剧碎片化。
  5. 不参与查询或排序的列

    • 结论:仅为“备用”而建的索引纯属资源浪费。
    • 原理:索引仅对 WHERE、JOIN、ORDER BY、GROUP BY 生效,无查询使用则无收益。

【深度推导/细节】

  • 索引维护的隐藏成本拆解:
    • Step 1:插入数据时,除写入数据页外,需在 B+Tree 所有相关索引中定位并插入键值(主键索引 + 每个二级索引)。
    • Step 2:若插入导致页空间不足(填充因子超过阈值),触发页分裂——分配新页、迁移部分数据、调整父节点指针,此过程阻塞并发写入。
    • Step 3:删除/更新数据产生死空间,需后台清理或重建索引,占用额外 I/O 与 CPU。
  • 临界点量化分析:
    • 若表写入 QPS > 1000,且查询频率低于写入的 10%,则索引可能使 TPS 下降 30%+。
    • 区分度阈值:通常认为重复值超过 30% 的列建索引收益骤降(例如 100 万行中某列只有 10 种值)。

【关联/对比】

  • 索引 vs 全表扫描的优化器选择:
    • MySQL 优化器通过 cardinality(索引基数)估算成本,若基数过小(如 < 总行数 5%),可能主动忽略索引。
  • 不同数据库的差异:
    • Oracle 支持位图索引,适用于低区分度但静态的场景;而 MySQL 的 B+Tree 索引对此类场景不友好。
  • 聚簇索引与非聚簇索引的维护差异:
    • InnoDB 中更新主键会导致所有二级索引同步更新(因二级索引存储主键值),代价远高于非聚簇索引架构(如 SQL Server 的非聚集索引)。

『面试官追问』

  1. 如何量化“写多读少”的具体比例?
    • 可通过监控 innodb_rows_inserted 与 innodb_rows_read 计算读写比,若写操作占比 > 70% 且查询响应时间要求不严,需谨慎建索引。
  2. 索引一定增加查询速度吗?何时索引会失效?
    • 否。索引失效场景:对索引列进行函数运算、隐式类型转换、使用 LIKE '%xx' 前导通配符、联合索引未遵循最左匹配、优化器误判扫描成本。
  3. 覆盖索引能否解决低区分度问题?
    • 部分解决。若查询仅需索引列数据(覆盖索引),可避免回表,但低区分度仍导致大量索引扫描,仅减少 I/O 次数而非扫描行数。

【版本差异】

  • MySQL 5.6 后索引条件下推(ICP):对低区分度索引,ICP 将 WHERE 条件过滤提前到存储引擎层,减少回表次数,但无法根本解决高重复值问题。
  • MySQL 8.0 不可见索引:可先创建索引并设为不可见,测试查询性能后再决定启用,避免直接投产影响写入。

【总结】
建立索引本质是空间换时间与写入换读取的权衡。决策前应分析:数据规模、读写比例、列区分度、查询模式。核心原则——只为加速关键查询路径的最小列集合建索引,并持续监控索引使用率(如 MySQL 的 sys.schema_unused_indexes)。

My avatar

文章看完了,不打算去别的篇章转转吗?👀 感谢你的耐心阅读!如果你有问题想探讨,或者发现了文中的 Bug,请务必通过页脚的链接联系我


More Posts

Comments