sys.schema_unused_indexes仅反映performance_schema开启后未被记录的I/O索引使用,不涵盖ORDER BY、FORCE INDEX、外键引用等场景,需结合EXPLAIN验证、冗余索引分析及外键/唯一约束检查后谨慎删除。

直接看 sys.schema_unused_indexes,但别信它说的“没用过”就真能删
MySQL 8.0+ 自带这个视图,查出来确实是“从未被 performance_schema 记录过读取”的索引,但它只反映开启采集后、实际触发 I/O 的使用情况。它不记录:ORDER BY 依赖、FORCE INDEX 硬编码、外键隐式引用、低频但关键的月结报表 SQL。所以结果只是起点,不是结论。
执行前先确认三件事:
- SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'performance_schema'; 返回必须是 ON
- UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME IN ('events_waits_current', 'events_statements_history_long');
- UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'wait/io/table/%';
做完这三步,再等至少 24 小时(覆盖完整业务周期),否则 COUNT_READ = 0 全不可信。
用 EXPLAIN FORMAT=TRADITIONAL 验证真实查询路径,重点盯 Extra 字段
很多索引根本不参与 WHERE 过滤,却支撑排序、分组或覆盖扫描。只看 key 字段为空就删,等于主动给查询加 Using filesort 或 Using temporary。
对所有高频 SQL(含定时任务、后台报表)跑一遍:
- SELECT id, name FROM users ORDER BY created_at LIMIT 10 —— 即使 created_at 索引在 sys.schema_unused_indexes 里,删掉就会全表扫
- SELECT COUNT(*) FROM orders WHERE status = 1 —— 主键或唯一索引可能被优化器隐式用于快速计数,不能单凭 COUNT_READ = 0 判定无用
- SELECT user_id, email FROM users —— 如果有 INDEX(user_id, email),Extra: Using index 表示走了覆盖索引,删了就得回表
人工比对 information_schema.statistics 识别前缀冗余,别只看字段名重复
冗余不是“名字像”,而是“功能可完全替代”。比如 INDEX(a, b) 存在时,INDEX(a) 就是冗余的;但 INDEX(a, b) 和 INDEX(a, c) 并不冗余,因为 b 和 c 不同。
查字段顺序: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(user_id, status) 和 INDEX(user_id) 并存 → 后者可删。
注意:CARDINALITY 值极低(比如 status 只有 3 个值,CARDINALITY ≈ 3)的单列索引,不属于冗余,属于“无筛选价值”,该删但逻辑不同。
删之前必须交叉验证外键、唯一约束和代码硬编码
误删一个被外键引用的索引,INSERT/UPDATE 会直接报错;误删唯一索引,可能导致数据重复;ORM 框架自动生成的 SELECT COUNT(*) 或 EXISTS 查询也可能依赖某个看似“安静”的索引加速。
查外键:SELECT CONSTRAINT_NAME, COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = 'db_name' AND TABLE_NAME = 'table_name' AND CONSTRAINT_NAME LIKE 'fk_%';
查唯一约束:SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = 'db_name' AND TABLE_NAME = 'table_name' AND CONSTRAINT_TYPE = 'UNIQUE';
搜代码库:grep -r "FORCE INDEX" .、grep -r "USE INDEX" .、grep -r "IGNORE INDEX" .
真正容易被忽略的是:有些索引既不在慢日志里出现,也不在 sys.schema_unused_indexes 中,但它支撑着某条低频但高权重的 DDL 或备份脚本里的 ORDER BY —— 这类索引得靠业务节奏和运维日志反推,没法全自动识别。


















