最可行路径是查performance_schema.table_io_waits_summary_by_index_usage,但须先确认performance_schema=on且wait/io/table/sql/handler仪器及对应consumers均已启用,再基于连续7天数据,排除主键、唯一索引、外键索引后分析count_star=0的索引,并结合explain、慢日志和代码库交叉验证。

直接查 performance_schema.table_io_waits_summary_by_index_usage 是最可行的路径,但必须先确认监控已真正生效——否则返回的全为 0,删错索引会直接导致线上故障。
确认 performance_schema 和索引统计是否真启用
很多人查出一堆 COUNT_READ = 0 就动手删,结果发现业务崩了。根本原因是:MySQL 默认不记录索引级 I/O 统计,哪怕 performance_schema = ON 也不代表能用。
- 先检查开关:
SHOW VARIABLES LIKE 'performance_schema';必须是ON - 再确认关键 instruments 是否启用:
SELECT * FROM performance_schema.setup_instruments WHERE NAME LIKE 'wait/io/table/sql/handler';—— 这个才是记录“索引实际被读”的底层仪器,ENABLED和TIMED都得是YES - 消费者也要开:
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME IN ('events_waits_current', 'events_waits_history_long'); - 重启或清空 history 后统计归零,所以“长期未使用”必须基于连续运行 ≥7 天的稳定采集数据
查 table_io_waits_summary_by_index_usage 时过滤掉干扰项
这个视图里大量 COUNT_READ = 0 的条目并不都该删——主键、唯一索引、外键索引即使没显式查询也可能被优化器隐式调用(比如 SELECT COUNT(*) 或约束校验)。
- 跳过主键和唯一约束索引:
WHERE INDEX_NAME IS NOT NULL AND COUNT_READ = 0 AND INDEX_NAME != 'PRIMARY' - 排除外键依赖:
SELECT CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'your_table' AND REFERENCED_TABLE_NAME IS NOT NULL;有结果就别碰对应索引 - 注意
COUNT_STAR = 0才表示完全没触发过 I/O;如果COUNT_STAR > 0但COUNT_READ = 0,可能是只用于排序(ORDER BY)或分组,这类索引不会产生COUNT_READ但仍有价值
交叉验证:EXPLAIN + 慢日志 + 强制提示扫描
仅靠 performance_schema 会漏掉三类情况:函数包装导致索引失效、应用层硬编码 FORCE INDEX、慢日志里高频但未进统计窗口的查询。
- 对疑似“无用”的索引字段组合,手动跑
EXPLAIN FORMAT=TREE SELECT ... WHERE ...,看key字段是否为空,同时确认possible_keys包含该索引名 - 搜代码库:
grep -r "FORCE INDEX\|USE INDEX" ./src/,尤其注意 ORM 框架生成 SQL 的地方 - 查慢日志中是否出现
Extra: Using index并明确带该索引名,哪怕频次低也说明它被依赖 - 前缀索引容易“伪失效”:比如
INDEX idx_name (name(10)),但业务查的值平均长度 12,这种不算真无用,而是设计缺陷
真正危险的不是 COUNT_READ = 0,而是你没查 information_schema.KEY_COLUMN_USAGE 就删了外键索引,或者没重放一遍 EXPLAIN 就信了视图数据——索引监控永远只是线索,不是判决书。











