冗余索引指功能重叠、可被其他索引完全替代的索引,如INDEX(a)与INDEX(a,b)共存时前者冗余;判断需结合查询条件、排序需求及字段顺序,工具仅提供线索而非结论。

怎么判断一个索引是不是冗余的
冗余索引本质是「功能重叠」:两个索引都能支撑同一条查询的 WHERE + ORDER BY,但其中一个字段更少、顺序更优或覆盖更全。比如 INDEX(a) 和 INDEX(a, b) 同时存在,前者就是冗余的——因为后者能完全替代前者的作用。
常见错误现象:EXPLAIN 显示某条慢查询用了 INDEX(a),但表里其实还有 INDEX(a, b, c);或者 SHOW INDEX 里看到多个以相同列开头的复合索引。
实操建议:
- 用
SELECT table_name, index_name, seq_in_index, column_name FROM information_schema.statistics WHERE table_schema = 'your_db' ORDER BY table_name, index_name, seq_in_index;查索引字段顺序 - 对每个表,按
index_name分组,看是否有前缀完全一致的索引(如idx_user_id和idx_user_id_status) - 注意隐式前缀匹配:如果已有
INDEX(user_id, status),再建INDEX(user_id)就是冗余的
哪些索引大概率是无用的
无用 ≠ 冗余,而是长期没被优化器选中。MySQL 5.7+ 提供了 sys.schema_unused_indexes 视图,但它依赖 performance_schema 的采集开关,且只统计「从未被使用过」的索引,不包括低频但关键的索引(比如每月跑一次的报表索引)。
使用场景:清理历史遗留库、上线前索引审计、大表迁移前瘦身。
实操建议:
- 确认
performance_schema已启用:SELECT * FROM performance_schema.setup_consumers WHERE NAME = 'events_statements_history_long';,并确保events_statements_history_long和table_io_waits_summary_by_index_usage消费者为ENABLED - 查真正未使用的索引:
SELECT object_schema, object_name, index_name FROM sys.schema_unused_indexes WHERE object_schema NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema'); - 别直接删!先用
SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = 'db' AND INDEX_NAME = 'idx_xxx';确认该索引是否被外键引用(FOREIGN KEY会隐式创建索引,删掉会导致 DDL 失败)
删除索引时容易踩的坑
最典型的坑是「删完发现某个 JOIN 变慢十倍」——不是索引真没用,而是你没覆盖所有执行计划路径。MySQL 的索引选择受 WHERE 条件、ORDER BY、LIMIT、统计信息、甚至临时表大小影响,同一语句在不同数据量下可能走不同索引。
参数差异:DROP INDEX 是 DDL 操作,在 MySQL 5.6+ 支持 ALGORITHM=INPLACE,但若索引涉及前缀长度(如 VARCHAR(255) 上建了 INDEX(col(10))),某些版本仍会触发表拷贝。
实操建议:
- 禁止在业务高峰执行
DROP INDEX;大表建议用pt-online-schema-change或 MySQL 8.0+ 的原子 DDL - 删之前用
EXPLAIN FORMAT=JSON对核心 SQL 重跑一遍,确认删掉的索引确实没出现在used_columns或possible_keys中 - 留意唯一索引和主键:即使看起来没被查询用到,也可能被
INSERT ... ON DUPLICATE KEY UPDATE或外键约束依赖
为什么不能光靠 sys.schema_redundant_indexes
这个视图只对比索引定义结构,不考虑实际查询模式。它会把 INDEX(a, b) 和 INDEX(a, b, c) 判定为冗余,但如果你有查询 WHERE a = ? AND b = ? ORDER BY c,那 INDEX(a, b) 就无法满足排序需求,删掉反而引发 filesort。
性能影响:冗余索引本身不拖慢查询,但会显著拖慢写入(每个 INSERT/UPDATE/DELETE 都要维护多个索引树)、增大缓冲池压力、延长备份恢复时间。
实操建议:
- 优先删「字段多、使用少、更新频繁」的索引,比如
INDEX(created_at, status, user_id, type)这种四字段索引,除非你真有对应查询,否则大概率是拍脑袋加的 - 用
SELECT * FROM sys.schema_index_statistics WHERE table_schema = 'db' ORDER BY rows_selected DESC LIMIT 10;找出被读最多、但 select 很少的索引(高 write / low read 是危险信号) - 记得定期更新统计信息:
ANALYZE TABLE your_table;,否则sys视图的数据可能滞后
索引是否冗余,得看查询怎么写、数据怎么分布、优化器怎么选;工具给的只是线索,不是判决书。尤其在分页深翻、范围查询、函数索引这些边界场景,字段顺序和覆盖度差一点,效果就天壤之别。










