可先用information_schema.statistics查列组合完全重复的索引,再结合performance_schema.table_io_waits_summary_by_index_usage、EXPLAIN验证及应用层检查确认是否冗余;删除需选低峰期并注意主从兼容性。

怎么快速找出 MySQL 里重复建的索引
MySQL 不会主动报错或警告冗余索引,但它们会拖慢写入、浪费内存、干扰优化器选路。最直接的办法是查 information_schema.statistics,按表 + 列组合聚合,看哪些索引前缀完全重叠。
- 用
SELECT table_name, index_name, GROUP_CONCAT(column_name ORDER BY seq_in_index) AS cols FROM information_schema.statistics WHERE table_schema = 'your_db' GROUP BY table_name, cols HAVING COUNT(*) > 1;能揪出「列顺序和内容完全一致」的多个索引 - 注意:
INDEX(a,b)和INDEX(a)是冗余的,但上面 SQL 不会直接标出——得手动比对前缀,INDEX(a,b,c)覆盖INDEX(a,b)和INDEX(a) - 别只看
SHOW INDEX输出,它不显示索引定义的逻辑覆盖关系,容易漏判
删除冗余索引前必须确认的三件事
删错索引可能让慢查询突然爆发,尤其在没做全链路压测的线上库上。重点不是“能不能删”,而是“有没有被用到”。
- 查
performance_schema.table_io_waits_summary_by_index_usage(MySQL 8.0+),看COUNT_STAR为 0 的索引,大概率闲置;但要注意:这个表重启后清零,且只统计开启监控后的访问 - 用
EXPLAIN对核心查询重跑一遍,把疑似冗余的索引临时禁用(ALTER TABLE t DROP INDEX idx_name),再对比执行计划是否变化——特别是key字段有没有换掉 - 检查应用层是否有
FORCE INDEX或 ORM 显式指定索引名,这类硬编码会直接炸掉
DROP INDEX 在主从架构下的实际影响
看起来只是删个元数据,但在大表上仍可能引发主从延迟甚至锁表,尤其 MySQL 5.7 及更早版本。
- MySQL 5.6+ 支持
ALGORITHM=INPLACE删除二级索引(不锁表),但需确认innodb_online_alter_log_max_size足够,否则会退化成 copy 表 - 主库执行
DROP INDEX时,DDL 语句会完整写入 binlog,从库回放期间如果索引还存在,不会报错;但如果从库版本低或配置不同,可能卡住 - 建议在低峰期操作,并提前在从库上
STOP SLAVE; START SLAVE;观察是否能顺利追平
复合索引顺序搞错导致“伪冗余”
很多人看到 INDEX(a,b) 和 INDEX(a,c) 就想删一个,其实它们并不冗余——因为 b 和 c 不同,无法互相替代。
-
WHERE a=1 AND b=2走INDEX(a,b),但走不了INDEX(a,c);反过来也一样 - 真正可删的是
INDEX(a)+INDEX(a,b)这种,前者完全被后者前缀覆盖 - 如果查询常带
ORDER BY b,那INDEX(a,b)比INDEX(a)价值高得多,不能只看字段数量判断冗余
冗余索引清理不是一锤子买卖,它依赖对查询模式的真实理解。线上库最危险的操作不是没删,而是删了之后没人盯慢日志——只要一条没覆盖的 WHERE 条件冒出来,就可能让某个接口响应时间从 20ms 跳到 2s。










