登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL sys.schema_unused_indexes 的结果为什么不能直接删索引

来源:17golang原创

时间:2026-09-14 23:57:36 327浏览 收藏

看到 sys.schema_unused_indexes 里出现一个索引,不能马上执行 DROP INDEX。这个视图表达的是:在当前 MySQL 实例、当前 Performance Schema 统计周期内,没有记录到该索引的 I/O 事件。它不是业务代码的“永久不用证明”。实例刚重启、报表只在月底运行、统计信息被清零,都会让结果暂时失真。

要点速览
  • schema_unused_indexes 是候选清单,先看观察窗口是否覆盖峰值、批处理和低频任务。
  • 索引可能承担唯一性、外键、排序或备用查询路径,不能只凭“没有事件”判断可删。
  • 先用 invisible index 做可恢复停用,再比较 EXPLAIN、慢查询和业务指标。
  • 真正删除前保留 SHOW CREATE TABLE 里的索引定义,并安排可重建方案。
MySQL sys.schema_unused_indexes 从 Performance Schema 索引事件汇总到候选索引的关系示意图
图1:统计观察示意图,展示未使用索引视图与 Performance Schema、业务工作负载之间的边界,不代表真实运行截图。

先把“未使用”还原成统计数据的快照

这个视图只有三个关键字段:库名、表名和索引名。它的上游是 performance_schema.table_io_waits_summary_by_index_usage,按表索引汇总 I/O 等待;其中 PRIMARY 表示使用主索引,NULL 表示没有使用索引,插入操作也会计入 INDEX_NAME = NULL。因此,视图回答的是“有没有被监测到事件”,不是“应用层是否设计上需要它”。

-- 先查看候选索引,并限定到目标业务库,避免误读系统库
SELECT object_schema, object_name, index_name
FROM sys.schema_unused_indexes
WHERE object_schema = 'shop'
ORDER BY object_name, index_name;

-- 同时记录统计来源;Performance Schema 数据属于当前实例,重启后不会保留
SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME,
       COUNT_STAR, COUNT_READ, COUNT_WRITE
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA = 'shop'
  AND OBJECT_NAME = 'orders';

MySQL 官方特别提醒:服务器必须运行足够久,而且处理的流量要能代表真实工作负载,否则出现在这个视图里的索引不一定有意义。维护前先记下实例启动时间、最近一次统计清零时间、DDL 变更时间,并确认观察窗口包含白天高峰、夜间任务、月末报表和故障补偿任务。若刚执行过 TRUNCATE TABLE performance_schema.table_io_waits_summary_by_table,之前的观察不能直接拿来做删除结论。

从表定义和真实 SQL 查出索引的隐藏职责

候选索引还要经过三层交叉检查。第一层是表定义:看它是否是唯一索引、外键相关索引,是否被某些排序或连接条件使用。第二层是应用代码、定时任务和运维脚本:低频导出、租户切换、数据修复往往不会出现在日常流量里。第三层是执行计划:对照核心 SQL 的 possible_keyskeyrowsExtra,确认优化器是否曾经把它作为可行路径。

-- 保存定义和可见性,后续可据此写回滚脚本
SHOW CREATE TABLE shop.orders;
SHOW INDEX FROM shop.orders;

-- 用真实条件观察优化器当前选择;示例 SQL 需替换成业务语句
EXPLAIN SELECT order_id, customer_id, created_at
FROM shop.orders
WHERE customer_id = 2088
  AND status = 'paid'
ORDER BY created_at DESC
LIMIT 50;
观察结果说明下一步
长时间无事件且无业务依赖更像冗余候选,但仍需灰度停用进入 invisible index 试验
只在低频窗口使用日常统计不足以代表全年负载延长观察,补看任务和报表
承担唯一性或约束语义性能视图不能覆盖完整数据语义保留,另行评估结构变更
EXPLAIN 计划会选择它候选清单与当前计划出现冲突先查统计清零、计划样本和版本差异

真正决定删除的是停用验证,不是候选清单

对普通二级索引,MySQL 支持把索引设为 INVISIBLE。默认情况下优化器不会使用它,但索引还在,恢复为 VISIBLE 比重新创建更容易。这个阶段要在副本、灰度实例或明确的低风险窗口进行,并为核心查询保留计划对比。

-- 先让优化器忽略索引,停用前确认它不是 PRIMARY KEY
ALTER TABLE shop.orders
  ALTER INDEX idx_customer_status INVISIBLE;

-- 逐条对比关键查询的计划;只对本次语句临时启用 invisible index
EXPLAIN SELECT /*+ SET_VAR(optimizer_switch = 'use_invisible_indexes=on') */
  order_id, customer_id, created_at
FROM shop.orders
WHERE customer_id = 2088 AND status = 'paid'
ORDER BY created_at DESC LIMIT 50;

-- 发现慢查询、错误或计划恶化时立即恢复
ALTER TABLE shop.orders
  ALTER INDEX idx_customer_status VISIBLE;

停用后观察的不只是单条 EXPLAIN:还要看慢查询数量、关键接口延迟、批处理耗时、锁等待和错误日志。官方文档列出的可见信号包括执行计划改变、原本不慢的查询进入慢查询日志,以及 Performance Schema 中受影响工作负载增加。若索引被 hint 明确引用,设为 invisible 还可能直接暴露错误,这正是提前灰度的价值。

MySQL invisible index 停用试验连接执行计划慢查询与恢复动作的静态关系示意图
图2:停用验证示意图,展示 invisible index 与查询计划、慢查询、Performance Schema 和恢复动作的关系,不代表真实运行结果。

满足删除条件后,保留一条可回滚的结构变更记录

只有当观察窗口覆盖真实任务、停用期间没有计划和业务指标恶化、索引也不承担约束语义时,才进入删除。删除本身会改变表结构,InnoDB 在线 DDL 是否适合当前表,还要结合表大小、并发写入、锁策略和发布窗口判断。

-- 删除前再次确认索引名,避免把相似名称写错
SHOW INDEX FROM shop.orders
WHERE Key_name = 'idx_customer_status';

-- 在已审批的变更窗口执行;具体 ALGORITHM/LOCK 需按环境评估
DROP INDEX idx_customer_status ON shop.orders;

-- 回滚脚本示例:保留原始列顺序和索引定义,禁止临时猜列
CREATE INDEX idx_customer_status
  ON shop.orders (customer_id, status);

最终记录至少包括:候选查询时间、实例运行和统计周期、索引原始定义、覆盖过的业务任务、停用开始与结束时间、关键 SQL 计划差异、指标结论以及重建 DDL。这样以后即使业务新增查询,也能知道这次删除基于什么证据,而不是把一行视图结果当成永久事实。

常见问题

视图里没有索引事件,是不是索引一定没用?

不是。它只代表当前实例和统计周期没有观测到事件,低频任务、重启或清零都可能造成空结果。

把索引设为 invisible 会立即释放磁盘吗?

不会。invisible 主要改变优化器是否考虑该索引,索引结构仍然存在;要释放空间需要经过正式删除和相应的表空间处理。

为什么 EXPLAIN 还会显示这个索引?

如果使用了 use_invisible_indexes=on 的会话设置或提示,优化器可以把 invisible index 纳入计划构造;排查时要记录会话级设置。

可以直接用 DROP INDEX 试错吗?

不建议。先 invisible 灰度并保留原始建索引语句,确认真实业务没有退化后再做结构变更。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>