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

废弃 MySQL 索引怎么安全下线:先设 INVISIBLE,再用 EXPLAIN ANALYZE 验证

来源:17golang原创

时间:2026-08-12 09:49:55 499浏览 收藏

线上跑的订单表堆着越来越多索引,写入性能越变越慢,时不时还碰到优化器选错执行路径的情况。真要删掉某个闲置旧索引的时候,大家怕的从来不是手滑输错DDL,而是某个跑了很久的低频查询突然从几十毫秒卡到几秒才能返回。MySQL 8.4 提供了一套更稳妥的过渡方案:先把待删候选索引设为 INVISIBLE,让优化器暂时感知不到它的存在,再用真实执行计划、实际运行耗时和慢查询记录交叉校验。

要点速览
  • ALTER INDEX ... INVISIBLE 比直接 DROP INDEX 回退成本低得多,出问题能快速恢复。
  • 判断索引能不能删,不能只盯着常用热门SQL,必须覆盖低频查询和全量写入链路。
  • EXPLAIN ANALYZE 负责对比实际执行的各项指标信息,INFORMATION_SCHEMA.STATISTICS 负责确认索引的可见状态。
  • 如果出现索引提示报错、慢查询量突增或者执行计划明显劣化,立刻把索引改回 VISIBLE

MySQL 索引下线为什么不能直接 DROP

假设 orders 表同时有 idx_user_statusidx_status_created 和一个历史遗留的 idx_user_created。最后这个索引可能只给很少用的报表查询提供服务,但它仍然会占用磁盘空间,每次执行INSERT、UPDATE操作时都要同步维护对应的B+Tree结构。

先把要处理的候选索引确认清楚,别光凭索引名字猜:

SELECT INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX, IS_VISIBLE
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'shop'
  AND TABLE_NAME = 'orders'
ORDER BY INDEX_NAME, SEQ_IN_INDEX;

这里的 IS_VISIBLE 就是索引当前的状态标识。主键不适合用这套下线流程,只有普通二级索引才是索引瘦身的主要处理对象。MySQL 官方也明确说明,索引数量过多会额外增加写入环节的维护开销,没必要给所有查询涉及的列都单独建索引。

先把候选索引设成 INVISIBLE,保留一键回退能力

等业务流量低谷的时候执行下面的操作:

ALTER TABLE orders
  ALTER INDEX idx_user_created INVISIBLE;

这一步不会真的删除索引数据,只修改优化器的索引候选池规则,把它从可见列表里移除。上层业务完全不会感知到异常,真要回退也非常简单:

ALTER TABLE orders
  ALTER INDEX idx_user_created VISIBLE;

执行下面的查询确认索引状态已经成功切换:

SELECT INDEX_NAME, IS_VISIBLE
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'shop'
  AND TABLE_NAME = 'orders'
  AND INDEX_NAME = 'idx_user_created';
MySQL orders 表在线查询经过 INVISIBLE 索引后暴露慢查询的等待链插画

不要把“查询还能返回结果”当成验证完成

索引设为隐身之后,常规功能测试大多仍然能返回正确结果,但这只能说明结果集内容没有错误。你还要同步观察执行计划、P95/P99延迟、慢查询日志,还有订单写入接口的锁等待时长和吞吐指标。低频SQL要从历史日志或者定时报表任务里捞出来逐一验证,不要只测首页列表这类高频接口。

用 EXPLAIN ANALYZE 对比计划和真实耗时

先把核心待验证查询的基线数据备份好,等索引隐身之后再重新执行:

EXPLAIN ANALYZE
SELECT id, user_id, created_at, status
FROM orders
WHERE user_id = 9012
  AND created_at >= '2026-08-01'
ORDER BY created_at DESC
LIMIT 50;

EXPLAIN 输出的是预估执行计划,EXPLAIN ANALYZE 会把整条SQL真的跑一遍,输出每一步迭代器的估算行数、实际返回行数、耗时和循环执行次数。重点关注是不是从原本的索引范围扫描退化成了大范围全表扫描,实际扫描行数是不是远大于优化器的估算值,排序操作是不是被迫落到了额外的临时文件步骤。

MySQL EXPLAIN ANALYZE 对比估算与实际耗时后决定保留或删除索引的证据插画

一个简短的判断表

观察结果处理建议
计划和耗时基本不变,写入压力下降继续观察后准备删除
低频查询明显变慢,慢日志出现新记录立即设回 VISIBLE,保留索引
只有单条 SQL 依赖该索引检查是否有索引提示,再评估改写 SQL
估算行数与实际行数相差很大先检查统计信息,不要急着删

确认可以删除前,再检查三类边界场景

第一类边界是代码里的索引提示。业务应用或者跑报表的SQL如果写了 USE INDEXFORCE INDEX,索引隐身之后这些语句可能直接抛出报错。第二类是时间边界:月末结算、夜间归档、退款对账这类定时查询不会出现在白天的业务流量里,很容易被漏掉。第三类是写入代价,索引删除前后都要记录订单批量导入、批量更新的操作耗时和锁等待情况。

所有检查项都通过之后,再执行真正的索引删除操作:

ALTER TABLE orders
  DROP INDEX idx_user_created;

如果还没覆盖完全部流量场景,建议继续保持索引的INVISIBLE状态延长观察期,稳一点总比出了线上故障再回滚好。删索引不是竞速任务,靠一步步验证拿到的确定性,比立刻省下那点磁盘空间要重要得多。

常见问题:Invisible Index 和 EXPLAIN ANALYZE

索引设为 INVISIBLE 后会不会马上释放磁盘?

不会。索引仍然完整存在,只是不参与默认优化器计划。真正释放空间要等到删除索引,再结合表空间和存储引擎的实际情况评估。

能不能只让一条 SQL 使用隐身索引?

可以用 SET_VAR(optimizer_switch = 'use_invisible_indexes=on') 的方式临时让优化器考虑隐身索引,适合做对比验证,不等于全局恢复索引可见。

为什么 EXPLAIN 变好了,线上还是变慢?

单条语句的计划不能代表全部流量。检查参数分布、缓存命中、锁等待、并发度和低频任务情况,优先用真实请求样本复测。

最后的下线清单

  • 确认是普通二级索引,记录名称、列顺序和依赖的关联SQL。
  • 切换为 INVISIBLE 后检查关键查询、慢日志和写入核心指标。
  • 用 EXPLAIN ANALYZE 对比估算值与实际值,覆盖全时段低频时间窗口。
  • 出现性能退化先切回 VISIBLE 完成回退,所有验证证据齐全后再执行 DROP INDEX。
声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>