必须手动启用performance_schema的wait/io/table/sql/handler instrument及对应consumer,否则table_io_waits_summary_by_index_usage为空;count_fetch为0不等于可删除,需排除主键、唯一约束、外键依赖及覆盖排序等隐式用途,并交叉验证sys.schema_unused_indexes。

performance_schema.table_io_waits_summary_by_index_usage 为什么查不到数据?
不是索引没被用,而是采集开关默认关着。MySQL 8.0 不会自动记录索引访问次数,必须手动启用 performance_schema 的底层 instrument 才能捕获真实 I/O 行为。
- 先确认
performance_schema已开启:SELECT @@performance_schema;返回1才行 - 启用核心采集项:
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME = 'wait/io/table/sql/handler'; - 同时打开消费者:
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'events_waits_current'; - 注意:
wait/io/table/sql/handler是关键,只开statements_digest或其他 instruments 无法统计索引读取
COUNT_FETCH 为 0 就能删索引吗?
不能直接删。这个值只说明“最近没被 SELECT/JOIN/ORDER BY 等触发读取”,但可能承担隐式职责。
- 主键(
PRIMARY)和唯一索引(UNIQUE KEY)即使COUNT_FETCH = 0,也不能删——它们保障数据完整性,不参与查询也可能被外键、约束或优化器内部路径依赖 - 某些查询看似绕过索引,实则靠它避免
Using filesort或Using temporary,比如SELECT id FROM t ORDER BY created_at LIMIT 10 - 函数包裹列(如
WHERE DATE(created_at) = '2025-01-01')会让EXPLAIN显示没走索引,但删了之后执行计划可能退化 - 务必结合
sys.schema_unused_indexes视图交叉验证,它已过滤掉约束类索引
如何判断一个索引是“低效”而非“未用”?
看三个指标的组合,而不是单看是否为零。
-
COUNT_FETCH极低(比如 COUNT_INSERT + COUNT_UPDATE + COUNT_DELETE 很高 → 该索引严重拖慢写入,几乎不加速查询,优先清理 -
SUM_TIMER_WAIT极小(比如 COUNT_FETCH 不为 0 → 可能只是全表扫描时顺带遍历聚簇索引,非有效命中 - 同一张表存在多个前缀重叠的索引(如
idx_a、idx_a_b、idx_a_b_c),且idx_a_b_c的COUNT_FETCH远低于idx_a_b→ 后者大概率冗余 - 用
EXPLAIN FORMAT=TREE重放高频 SQL,确认using index condition或using index是否真实出现在目标索引上
DROP INDEX 前最容易忽略的三件事
删索引比建索引风险更高,尤其在大表上。很多线上事故源于跳过这几步。
- 检查
INFORMATION_SCHEMA.STATISTICS中NON_UNIQUE = 0 AND INDEX_NAME != 'PRIMARY'的索引——这些是唯一索引,应用层可能依赖其报错逻辑做业务判断 - 确认没有工具或中间件显式引用该索引名,比如某些 ORM 的 hint、CDC 组件的白名单、审计日志规则
- 在低峰期执行,并提前加锁评估影响:
SELECT * FROM t WHERE 1=0 FOR UPDATE;测试是否触发元数据锁等待;对千万级表,建议用ALTER TABLE t DROP INDEX idx_name, ALGORITHM=INPLACE, LOCK=NONE;(需满足 8.0+ 条件)
实际操作中,最常被低估的是时间窗口问题:performance_schema 的统计有秒级延迟,刚跑完的查询未必立刻反映在 COUNT_FETCH 里。连续观察 48 小时以上再下结论,比单次快照可靠得多。











