安全确认索引未被使用需三步:查performance_schema.table_io_waits_summary_by_index_usage中COUNT_STAR和COUNT_READ均为0;用EXPLAIN验证所有高频SQL的key字段;检查慢查询日志是否含该索引。

怎么安全确认一个索引是否真没被用过
MySQL 不会自动记录“谁查了哪个索引”,所以不能光看 SHOW INDEX 或表结构就下结论。得结合实际查询路径和统计信息来交叉验证。
实操建议:
- 查
performance_schema.table_io_waits_summary_by_index_usage(MySQL 8.0+),重点关注COUNT_STAR为 0 且COUNT_READ为 0 的索引;注意该视图默认可能关闭,需确认performance_schema已启用且相关消费者已打开 - 用
EXPLAIN跑一遍业务中所有高频SELECT、UPDATE、DELETE语句,看key字段是否出现目标索引名;别漏掉带OR、IN、函数包裹列的查询,它们容易绕过预期索引 - 检查慢查询日志里有没有用到该索引的语句(搜索
key: index_name或Extra: Using index配合索引名)
DROP INDEX 时为什么有时卡住或报错
不是语法写错,而是 MySQL 在删索引前要做元数据锁(MDL)和表级操作校验,尤其在高并发写入场景下容易暴露问题。
常见错误现象:
-
ERROR 1091 (42000): Can't DROP 'idx_name'; check that column/key exists:索引名拼错,或大小写不匹配(Linux 下表名/索引名区分大小写) - 命令长时间无响应:其他事务正持有该表的
MDL_SHARED_WRITE锁(比如长事务、未提交的UPDATE),DROP INDEX必须等它释放 -
ERROR 1025 (HY000): Error on rename...:发生在 MyISAM 表上,或 MySQL 5.7 以下版本对主键索引的误操作(主键不能用DROP INDEX删除)
稳妥做法是先执行 SELECT * FROM performance_schema.metadata_locks WHERE OBJECT_SCHEMA = 'db_name' AND OBJECT_NAME = 'tbl_name'; 看锁状态,再决定是否杀掉阻塞事务。
删除索引后性能反而变差?这些隐性依赖要提前扫
索引不只是加速 WHERE,还影响排序(ORDER BY)、分组(GROUP BY)、覆盖扫描(Using index),甚至外键约束和唯一性保障。
容易踩的坑:
- 以为只用在
WHERE条件里的索引可以删,结果发现某个ORDER BY created_at LIMIT 10查询没了索引后从filesort变成全表扫描 - 唯一索引(
UNIQUE KEY)被删,导致应用层重复插入不报错,数据一致性被破坏 - 外键字段没索引(MySQL 要求外键列必须有索引),删掉后后续
INSERT/UPDATE外键关联行会变慢,甚至报错ERROR 150 - 联合索引删掉前缀列索引(如已有
(a,b,c),又建了单独的(a)),删后者看似冗余,但某些WHERE a = ? ORDER BY b场景下优化器可能更倾向用单列索引做排序
批量清理索引时如何控制风险
别一次性删多个,尤其在线上核心表。MySQL 删索引本质是重建表(除 ALGORITHM=INSTANT 支持的极简变更外),耗时和锁表现差异很大。
关键判断点:
- 用
SHOW CREATE TABLE tbl_name确认存储引擎:InnoDB 支持ALGORITHM=INPLACE(5.6+),但删二级索引仍是轻量级操作;MyISAM 必须复制整个 .MYI 文件,锁表时间长 - MySQL 8.0+ 对非主键二级索引支持
ALGORITHM=INSTANT,命令形如ALTER TABLE t DROP INDEX idx_name, ALGORITHM=INSTANT;,几乎不锁表;但前提是没用到全文索引、空间索引等不支持类型 - 大表删索引前,先在从库或影子库上跑一遍,观察
ALTER耗时和innodb_buffer_pool_wait_free是否飙升 - 删完立刻查
information_schema.STATISTICS确认索引消失,再用EXPLAIN验证关键语句执行计划没退化
真正麻烦的从来不是“删不删”,而是“删完谁在用、谁不知道自己在用”。线上删索引前,最好把最近一周的 slow_log 和应用层 SQL 日志拉出来 grep 一遍索引名——人眼比文档更可靠。


















