人人都会AI编程

12.5 索引监控与冗余、重复索引清理

更新时间:2026-07-11

索引不是建完就一劳永逸的。随着业务演进,有些索引可能从未被使用,有些索引功能重复,还有些索引因为表结构变更变成了负资产。定期监控和清理索引,不仅能节省磁盘空间,还能减少写入时的索引维护开销,让数据库跑得更轻快。

12.5.1 查看索引使用情况:哪些索引从未被用过

MySQL 提供了索引使用统计,可以告诉你每个索引被访问的次数,但需要特别留意——这些统计是“自上次启动以来”的累计值,重启即清零。

方法一:通过 performance_schema 查看

从 MySQL 5.6 开始,performance_schema 库中提供了 table_io_waits_summary_by_index_usage 表,可以统计索引的读写次数:

SELECT
    object_schema AS `库`,
    object_name   AS `表`,
    index_name    AS `索引`,
    count_read    AS `读次数`,
    count_write   AS `写次数`
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
  AND object_schema NOT IN ('mysql', 'performance_schema', 'sys')
ORDER BY count_read ASC, count_write ASC;

如果某个索引的 count_readcount_write 都为 0,说明它自统计以来既没有被查询使用过,也没有因写入而被维护触发过(注意:写入时即使不查询,索引本身也会被更新,所以 count_write 通常是大于 0 的)。更常见的情况是 count_read = 0,意味着这个索引从未参与过查询加速,它的存在价值就值得怀疑。

方法二:通过 sys 库视图

sys 库是 MySQL 5.7 引入的一组便捷视图,它将 performance_schema 的复杂查询封装得更易用。schema_unused_indexes 视图直接列出了从未被使用的索引:

SELECT * FROM sys.schema_unused_indexes;

这个视图会自动过滤掉主键和唯一约束对应的索引(因为这些通常有保证唯一性的核心作用,不能随便删除),只展示那些纯粹的“普通索引”且从未被用到的情况。如果你看到某个索引在这里出现,而你又确认该索引不是最近新建的(刚建的索引统计肯定为 0),那它有很大概率是冗余的。

12.5.2 重冗余索引与重复索引的识别

监控到未使用的索引后,先别急着删,还要判断它是不是真的无价值。有些索引可能暂时没用,但在月终报表、数据对账等低频场景中依然发挥作用。更“该删”的其实是以下两类索引:

1. 重复索引

重复索引指在同一张表中,多个索引覆盖的组合列和顺序完全一致,或者本质上是同一个索引。比如:

-- 这两个索引完全重复
INDEX idx_a (user_id, create_time)
INDEX idx_b (user_id, create_time)

这种情况通常是多人协作、反复添加索引时产生的。MySQL 允许存在同名字段但不同名的重复索引,它们白白浪费磁盘和维护开销。

2. 冗余索引

冗余索引是指某个索引的功能已经被另一个索引完全覆盖,无需单独存在。最典型的是:

INDEX idx_user (user_id)
INDEX idx_user_time (user_id, create_time)

在这个例子中,idx_user 是冗余的。因为 idx_user_time 是联合索引,且以 user_id 开头,它完全可以替代 idx_user 做等值查询和排序。保留 idx_user 只会增加维护成本,没有额外性能收益。

可以用 sys.schema_redundant_indexes 视图快速发现这类情况:

SELECT * FROM sys.schema_redundant_indexes;

它会列出冗余索引及其“覆盖它的”索引,并给出删除建议。不过这个视图不会深入考虑索引长度、唯一性等细节,只能作为一个快速筛查工具,不能完全代替人工判断。

12.5.3 人工辅助判断是否可删除

在决定删除一个索引前,建议做最终的确认:

  • 确认统计周期足够长:如果 MySQL 实例刚重启不久,统计值可能为零。至少要经过一个完整的业务周期(包含日报、周报等批处理)后,统计才有参考意义。
  • 通过 EXPLAIN 验证:模拟低频查询的 SQL,使用 EXPLAIN FORMAT=JSON 看看该索引是否出现在 possible_keys 中。如果它永远不在候选索引里,那通常是安全的。
  • 检查是否有外键依赖:外键在关联列上隐式要求索引,如果删除索引可能导致某些关联操作变慢甚至报错(MySQL 会自动为外键创建索引,但有时名不符),需确认影响。
  • 利用不可见索引(MySQL 8.0+)做灰度测试:这是最安全的做法。先用 ALTER TABLE ... ALTER INDEX idx_name INVISIBLE; 将索引设为不可见,观察数天业务是否有慢查询出现。如果一切正常,再执行 ALTER TABLE ... ALTER INDEX idx_name VISIBLE; 恢复,或者直接 DROP INDEX

12.5.4 如何安全地清理索引

在确认某索引确实冗余且无影响后,清理操作本身很简单:

ALTER TABLE 表名 DROP INDEX 索引名;

但是要特别注意:

  • 主键索引和唯一索引不能轻易删除:它们不仅仅是加速查询,更承担着数据完整性约束的功能。
  • 在业务低峰期执行DROP INDEX 在 InnoDB 上是一个相对快速的元数据操作,但大表上仍然可能引发短暂的锁等待。
  • 一次只删一个:避免一次删除多个索引导致的连锁影响难以排查。
  • 保留修改记录:将删除的索引结构、删除时间、理由记录下来,方便未来回溯。

12.5.5 建立长效监控习惯

索引管理应该成为一种常态,而不是出了问题才去翻。建议:

  • 每月跑一次 sys.schema_unused_indexessys.schema_redundant_indexes,生成报告。
  • 对新建表索引有审批流程,避免随意添加。
  • 利用监控系统(如 PMM、Prometheus 的 MySQL Exporter)持续跟踪索引读写比例,及时发现异常。

定期清理冗余索引,相当于给数据库做一次“瘦身体检”。它通常不会带来惊天动地的性能提升,但能在长期运行中持续降低 CPU 和磁盘开销,尤其对写入频繁的大表,收益相当实在。