索引不是建完就一劳永逸的。随着业务演进,有些索引可能从未被使用,有些索引功能重复,还有些索引因为表结构变更变成了负资产。定期监控和清理索引,不仅能节省磁盘空间,还能减少写入时的索引维护开销,让数据库跑得更轻快。
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_read 和 count_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_indexes和sys.schema_redundant_indexes,生成报告。 - 对新建表索引有审批流程,避免随意添加。
- 利用监控系统(如 PMM、Prometheus 的 MySQL Exporter)持续跟踪索引读写比例,及时发现异常。
定期清理冗余索引,相当于给数据库做一次“瘦身体检”。它通常不会带来惊天动地的性能提升,但能在长期运行中持续降低 CPU 和磁盘开销,尤其对写入频繁的大表,收益相当实在。