应删除长期未被使用或与其他索引重复的索引;通过information_schema.statistics和performance_schema.table_io_waits_summary_by_index_usage确认使用情况,冗余复合索引如INDEX(a,b)与INDEX(a)共存时通常可删INDEX(a)。

什么时候该删掉一个索引
索引不是越多越好,冗余或低效的索引会拖慢写入、占用磁盘、增加优化器决策负担。判断要不要删,核心看两点:有没有被用到,以及是否和其他索引重复。
先查 information_schema.statistics 或用 SHOW INDEX FROM table_name 看索引定义;再结合 performance_schema.table_io_waits_summary_by_index_usage(MySQL 8.0+)确认实际命中次数——长期 COUNT_STAR = 0 的索引基本可以标记为待清理。
- 复合索引
INDEX (a, b)和单独索引INDEX (a)共存时,后者大概率冗余(除非有WHERE a = ? ORDER BY b DESC这类需要单独排序的场景) -
ALTER TABLE ... DROP INDEX不锁表(Online DDL),但大表仍建议在低峰期执行 - 删除前用
EXPLAIN SELECT ...对比关键查询的执行计划,防止误伤
如何安全地重建低效索引
索引碎片化、统计信息过期、页分裂严重时,SELECT 性能会下降,但直接 DROP + CREATE 有风险:中间空窗期查询可能走全表扫描,且大索引重建过程阻塞写入(尤其 MySQL 5.7 及更早版本)。
- 优先用
OPTIMIZE TABLE table_name(InnoDB 下本质是重建表 + 索引,需额外磁盘空间) - MySQL 5.6+ 支持
ALTER TABLE ... ALGORITHM=INPLACE, LOCK=NONE重建单个索引,例如:ALTER TABLE t1 DROP INDEX idx_a, ADD INDEX idx_a (a) ALGORITHM=INPLACE - 重建后务必执行
ANALYZE TABLE table_name,否则优化器可能继续用旧的统计信息做错误判断
哪些操作会意外让索引失效
不是 SQL 写错才失效,很多看似合理的写法在底层无法走索引,比如隐式类型转换、函数包裹字段、模糊查询左匹配等。
-
WHERE phone = 13800138000(phone 是 VARCHAR)→ 触发隐式转换,索引失效 -
WHERE DATE(create_time) = '2024-01-01'→ 函数作用于索引列,无法使用索引 -
WHERE name LIKE '%abc'→ 左模糊,B+Tree 无法从头定位 -
WHERE status IN (1,2,3) AND create_time > '2024-01-01'→ 如果复合索引是(create_time, status),顺序反了也用不上
监控索引使用情况的最小可行方案
不依赖外部工具,只用 MySQL 自带能力就能快速摸清索引真实价值。重点不是“有没有”,而是“谁在用、怎么用、用了多少次”。
- MySQL 8.0+:查
performance_schema.table_io_waits_summary_by_index_usage,关注COUNT_READ字段,值为 0 且存在超过一周可预警 - 所有版本:开启慢查询日志 +
log_queries_not_using_indexes = ON,但注意它只记录“完全没走索引”的语句,对部分走索引的无效查询无感知 - 定期跑
SELECT * FROM sys.schema_unused_indexes(sys schema 需要初始化),它基于 performance_schema 数据聚合,比手写 SQL 更准
真正麻烦的不是建错索引,而是没人定期看它还活不活着。索引没有自动退休机制,得靠人盯住那些半年没被 SELECT 触碰过的 INDEX 名字。










