mysql删除索引需分四步:先确认performance_schema启用且数据真实;再用sys.schema_unused_indexes查未被select读取的索引;接着用sys.schema_redundant_indexes识别结构冗余索引;最后人工交叉验证聚合依赖、低基数、orm隐式使用等风险,逐个删除并监控。

不能直接“按利用率排序删索引”,MySQL 的 sys 库不提供索引使用率百分比,也没有“利用率最低”这种可排序的量化指标。所谓“快速盘点并删除”,本质是分两步走:先用 sys.schema_unused_indexes 找出长期未被读取的索引,再用 sys.schema_redundant_indexes 找出结构上可被覆盖的索引,两者交叉比对、人工验证后谨慎删除。
第一步:确认 performance_schema 数据真实可用
这是所有判断的前提,否则查出来的“未使用”全是假信号:
- 检查 performance_schema 是否开启:
SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'performance_schema';—— 必须返回 ON - 确认关键消费者已启用:
SELECT NAME, ENABLED FROM performance_schema.setup_consumers WHERE NAME IN ('events_statements_history_long', 'table_io_waits_summary_by_index_usage');—— 两行都必须是 YES - 确保已运行足够时长(建议 ≥ 7 天),覆盖完整业务周期(含定时任务、报表、大促流量等);刚重启或低峰期采集的数据不可信
第二步:查出真正“没被 SELECT 读过”的索引
sys.schema_unused_indexes 只反映 COUNT_FETCH = 0,即自数据采集以来一次都没被 SELECT 查询命中过索引扫描:
- 安全查询写法(排除系统库和约束索引):
SELECT object_schema, object_name, index_name FROM sys.schema_unused_indexes WHERE object_schema NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema') AND index_name != 'PRIMARY' AND index_name NOT IN (SELECT CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE CONSTRAINT_SCHEMA = object_schema AND TABLE_NAME = object_name AND CONSTRAINT_TYPE IN ('UNIQUE', 'FOREIGN KEY')); - 重点验证:对结果中的每个索引,执行
SELECT COUNT_FETCH FROM performance_schema.table_io_waits_summary_by_index_usage WHERE OBJECT_SCHEMA = 'db' AND OBJECT_NAME = 'tbl' AND INDEX_NAME = 'idx';—— 确保确实为 0,而非因统计清零导致误报 - 注意:聚合类 SQL(如
SELECT COUNT(*) FROM t WHERE status=1)可能走索引但COUNT_FETCH值极低,不能仅凭“未出现在该视图”就判定无价值
第三步:识别结构冗余,不看是否“被用过”
sys.schema_redundant_indexes 基于 DDL 分析,只管定义是否重叠,不管实际使用:
- 执行:
SELECT table_schema, table_name, redundant_index_name, redundant_index_columns, dominant_index_name, dominant_index_columns FROM sys.schema_redundant_indexes; - 典型冗余场景:
• 同时存在INDEX(a)和INDEX(a,b)→ 前者被标冗余
• 存在UNIQUE(a)和普通INDEX(a)→ 后者被标冗余 - 但它不识别:
•INDEX(a,b)和INDEX(a,b,c,d)并存(即使 c/d 列从不参与查询)
•INDEX(b,a)与INDEX(a,b)(顺序不同,结构不构成前缀包含)
• ORM 中显式FORCE INDEX的索引(即使逻辑冗余也不能删)
第四步:人工交叉验证三处硬点,再决定删不删
自动输出的“可删列表”只是线索,不是结论。必须逐条确认:
-
是否支撑聚合查询?例如
SELECT COUNT(*) FROM orders WHERE pay_status = 'paid'可能依赖INDEX(pay_status)快速走索引 count,这类索引COUNT_FETCH往往很低,但绝不能删 -
CARDINALITY 是否极低?查
INFORMATION_SCHEMA.STATISTICS,若status列只有 3 个值却建了索引,CARDINALITY ≈ 3,这种索引本就不该存在,哪怕COUNT_FETCH > 0也建议删 -
ORM 或定时脚本是否隐式依赖?比如 Django 的
.count()、Laravel 的exists()、或凌晨跑的 Python 脚本里写了WHERE a = ? ORDER BY b—— 这些不会出现在慢日志,但可能强依赖某个短索引
删之前务必备份:用 mysqldump -u -p --no-data your_db | grep 'CREATE INDEX' 导出当前所有索引定义,留作回滚依据。删除命令统一用 ALTER TABLE db.tbl DROP INDEX idx_name;,每次只删一个,观察 24 小时监控(QPS、慢查、写入延迟)无异常再进行下一个。











