mysql中死索引指创建后未被查询使用的非主键/唯一索引,可通过performance_schema.table_io_waits_summary_by_index_usage中count_read=0识别;过期索引则因字段类型变更、函数包装、前缀长度不足或联合索引顺序错位导致失效,需结合explain分析执行计划判断。

怎么识别 MySQL 里的死索引(没被用过的索引)
MySQL 自身不记录“某个索引从创建起就没被查过”,但可以通过 performance_schema 中的统计信息反推。关键看 table_io_waits_summary_by_index_usage 表里 COUNT_READ 为 0 且非主键/唯一约束索引的条目。
- 必须开启
performance_schema,且至少启用events_waits_history_long和相关 instrument(默认 8.0+ 通常已开) - 只对开启监控后发生的查询生效,重启 MySQL 或清空 history 后计数归零
- 主键和唯一索引即使没显式查询也可能被优化器隐式使用(比如
SELECT COUNT(*)),不能单凭COUNT_READ = 0就判定无用 - 示例查询:
SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME FROM performance_schema.table_io_waits_summary_by_index_usage WHERE INDEX_NAME IS NOT NULL AND COUNT_READ = 0 AND INDEX_NAME NOT IN ('PRIMARY');
如何发现过期索引(字段类型或业务逻辑变更后失效的索引)
这类索引不是“没被用”,而是“用了也白用”——查询条件无法命中、或者优化器根本不会选它。典型表现是执行计划里 type 为 ALL 或 index,而本该走 ref/range。
- 检查
EXPLAIN输出中key字段是否为空,同时确认possible_keys包含该索引名 - 字段类型变更后易失效:比如把
VARCHAR(255)改成TEXT,或加了函数包装(WHERE UPPER(name) = 'A'),原索引就失效 - 前缀索引长度不够也会“过期”:比如建了
INDEX idx_name (name(10)),但常见查询值长度 >10,实际无法利用 - 联合索引顺序错位:如索引是
(a,b,c),但查询只用WHERE b = ?,该索引完全不可用
mysql无用索引统计工具推荐与限制
没有银弹工具,但 pt-index-usage(Percona Toolkit)和 sys.schema_unused_indexes(MySQL 8.0+ 自带视图)最常用,二者原理不同,结果不能直接互换。
-
pt-index-usage分析慢日志或通用日志,依赖日志覆盖度;若线上没开慢日志或采样率低,会漏判 -
sys.schema_unused_indexes底层仍查performance_schema,但它会过滤掉外键索引、全文索引等特殊类型,且不区分“真无用”和“低频但关键” - 两者都不检测“仅用于排序/分组但没在 WHERE 中出现”的索引(比如
ORDER BY created_at单独建的索引),这类索引可能仍有价值 - 运行
SELECT * FROM sys.schema_unused_indexes;前,先确认performance_schema的采集时间足够长(建议 ≥7 天)
删索引前必须验证的三件事
误删索引比留着更危险——可能让某条关键报表查询从 100ms 暴涨到 10s。删之前务必确认:
- 该索引是否被任何存储过程、触发器、事件或定时任务中的 SQL 引用(搜代码库 + 查
information_schema.ROUTINES) - 是否存在未捕获的低频但高权重查询(比如凌晨跑的结算任务),它们可能不在慢日志里,但严重依赖这个索引
- 在从库或影子库上先
DROP INDEX,用相同流量回放(如pt-query-digest --replay),观察 QPS、延迟、Handler_read_next是否异常升高
真正难的不是找无用索引,而是判断“它到底在哪个你没想到的地方,悄悄扛着压力”。监控数据永远滞后于业务变化,人工核验绕不开。











