垃圾索引指长期未被优化器选用、功能被其他索引覆盖或无法支撑任何有效查询路径的索引,删除前须通过sys.schema_unused_indexes(需performance_schema采集开启且运行超24小时)、information_schema.statistics比对前缀覆盖关系,并验证外键依赖、force index硬编码及order by/group by隐式需求,删后需analyze table更新统计信息并视碎片情况执行optimize table回收空间。

垃圾索引不是语法错误,而是长期不被优化器选用、功能被其他索引覆盖、或根本无法支撑任何有效查询路径的索引。直接删可能引发慢查询爆发,必须先验证再动手。
查 sys.schema_unused_indexes 找真正没用过的索引
MySQL 8.0+ 自带这个视图,比人工扫表靠谱得多,但它依赖 performance_schema 的采集状态:
- 先确认采集已开启:
SELECT * FROM performance_schema.setup_consumers WHERE NAME IN ('events_statements_history_long', 'table_io_waits_summary_by_index_usage');两个都得是ENABLED - 运行至少 24 小时以上(尤其要覆盖业务高峰和定时任务周期),否则统计为空白
- 执行:
SELECT object_schema, object_name, index_name FROM sys.schema_unused_indexes WHERE object_schema NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema'); - 注意:它只标“从未被用过”,但像月结报表这类低频但关键的索引不会被标记——得结合慢查询日志交叉验证
用 information_schema.statistics 识别冗余联合索引
冗余不是“重复”,而是“前缀覆盖”:一个索引能完全替代另一个的作用。比如 INDEX(a,b) 存在时,INDEX(a) 就是冗余的。
- 查字段顺序:
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; - 重点看同表下多个索引是否共享相同前缀列,例如:
idx_user_id和idx_user_id_status共存 → 前者大概率冗余 -
INDEX(a,b)和INDEX(a,c)不冗余,因为b和c不同,无法互相替代;但INDEX(a,b)和INDEX(a,b,c)并存时,前者通常可删 - 别忽略
CARDINALITY:如果status列只有 3 个值,CARDINALITY接近 3,建索引基本无效,但它是单列索引,不属于“冗余”,属于“无用”
删之前必须验证三件事
删索引不是删文件,DDL 操作在大表上仍可能锁表、拖慢主从同步,且不可逆。
- 确认没被外键隐式依赖:
SELECT constraint_name FROM information_schema.key_column_usage WHERE table_schema = 'your_db' AND table_name = 'your_table' AND column_name = 'xxx';如果有结果,说明该列参与外键,对应索引不能删 - 检查 ORM 或定时任务是否硬编码了
FORCE INDEX—— 这类 SQL 在索引删除后会直接报错或退化成全表扫描 - 在从库或测试环境执行:
ALTER TABLE your_table DROP INDEX idx_name;,然后对核心业务 SQL 跑EXPLAIN,对比key和rows是否恶化;尤其注意ORDER BY和GROUP BY场景,它们也依赖索引
清理后别忘了碎片和空间回收
删掉索引只是释放元数据,物理空间不会自动归还给操作系统,.ibd 文件大小不变。
- 查碎片:
SELECT TABLE_NAME, ROUND(DATA_FREE / 1024 / 1024, 2) AS free_mb FROM information_schema.tables WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'your_table';若free_mb> 总空间 15%,建议整理 - 回收空间用:
OPTIMIZE TABLE your_table;(MySQL 5.7+)或ALTER TABLE your_table ENGINE=InnoDB;(兼容性更好) - 注意:这两个命令都会重建表,产生锁和 I/O 压力,务必安排在低峰期,且确认 binlog_format 是
ROW,避免主从异常
最常被忽略的点是统计信息过期:删完索引后,如果不执行 ANALYZE TABLE your_table;,优化器可能基于旧的统计误判执行计划,导致本该走的新索引被跳过。











