最准确方法是查performance_schema.table_io_waits_summary_by_index_usage中COUNT_READ是否为0,需先启用events_waits_history_long消费者;再确认索引未被外键引用、非主键或唯一约束、无ORM迁移依赖;删除统一用ALTER TABLE DROP INDEX更安全。

怎么确认一个索引是不是无效的
无效不等于“没被EXPLAIN显示出来”,而是长期没被查询引擎真正用过。直接查 performance_schema.table_io_waits_summary_by_index_usage 最准,但得先开开关:
- 确保
performance_schema已启用(默认 MySQL 5.6+ 都开) - 执行
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'events_waits_history_long'; - 再跑
SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, COUNT_READ FROM performance_schema.table_io_waits_summary_by_index_usage WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table' AND INDEX_NAME = 'idx_name';
如果 COUNT_READ 是 0 或极低(比如一周内 EXPLAIN 的 key_len 小就删——复合索引 (a,b,c) 在 WHERE a=1 AND b>10 下只用前两列,key_len 短不代表它没用。
删之前必须检查的三件事
跳过这步容易触发报错或线上故障:
-
ERROR 1553 (HY000): Cannot drop index 'xxx': needed in a foreign key constraint—— 先查SELECT CONSTRAINT_NAME, COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_NAME = 'your_table' AND REFERENCED_TABLE_NAME IS NOT NULL;,确认该索引没被外键引用 - 是不是主键或唯一约束?
SHOW INDEX FROM your_table里看Non_unique字段:0 表示唯一(含主键),不能用DROP INDEX直删,得用ALTER TABLE your_table DROP PRIMARY KEY或DROP INDEX(MySQL 8.0.19+ 支持DROP CONSTRAINT) - 有没有 ORM 或迁移脚本依赖这个索引名?比如 Laravel 的
php artisan migrate:rollback可能反向重建索引,手动删了会导致迁移失败
用 ALTER TABLE DROP INDEX 还是 DROP INDEX ON?
MySQL 8.0+ 官方明确标记 DROP INDEX idx_name ON tbl_name 为 legacy syntax,虽仍可用,但有隐患:
- 语法上看似“独立”,实则在 InnoDB 中必须绑定表,和
ALTER TABLE本质一样 - 容易误写成跨表操作(比如漏写
ON tbl_name),报错ERROR 1064 -
ALTER TABLE tbl_name DROP INDEX idx_name显式、全引擎兼容、语义清晰,应作为默认选择
示例:ALTER TABLE users DROP INDEX idx_created_at; —— 这条比 DROP INDEX idx_created_at ON users; 更安全、更易维护。
删完之后不能马上放心
删除动作本身很快,但影响可能滞后:
- 删的是高频查询字段的索引?立刻查慢查询日志,看是否有 SQL 执行时间突增
- 写入变快了,但磁盘 I/O 压力是否转移到其他索引或 buffer pool?监控
Innodb_buffer_pool_read_requests和Innodb_data_reads - 大表删索引会拿 MDL 写锁,期间所有 DML 都阻塞;如果删完发现应用卡顿,大概率是长事务没释放锁,不是索引本身的问题
真正容易被忽略的,是统计信息没更新——删完索引后,记得跑一次 ANALYZE TABLE your_table;,否则优化器可能还按旧分布做执行计划。


















