Skip to content

不可见索引 Invisible Index ​

⭐⭐索引设计与失效8.0不可见索引invisible安全删索引8.0新特性

想删索引又怕误伤:不可见索引先探路 ​

商品表按 category 查询,idx_category 索引正常工作。DBA 怀疑这个索引已经没用了--业务查询都改走了别的索引,idx_category 只在白白增加写入开销和占用空间。想删掉它,但又怕有隐藏的慢查询依赖这个索引。

sql
-- idx_category 可见时,按 category 查询走索引
SELECT id, product_name, category, price
FROM t_product_index
WHERE category = '手机';

直接 DROP INDEX 风险很大:万一有某个低频但关键的查询依赖该索引,删除后会骤降为全表扫描;删除后再加回索引需要重新构建,大表上耗时且锁表。生产环境很难事先穷举所有受影响的 SQL。

MySQL 8.0 的 INVISIBLE 索引就是解决这个痛点的--先把索引设为不可见,优化器不再使用它,但索引数据仍被维护。观察一段时间确认无影响后再安全删除,若有问题瞬间恢复。

真实场景

索引清理是 DBA 最头疼的操作之一。删了怕出事,不删怕浪费。很多公司的表上堆积了大量"可能没用了"的索引,谁也不敢动。INVISIBLE 索引让索引清理从"赌博"变成"可回滚的灰度验证"。

问题分析 ​

bad.sql ​

sql
-- bad.sql: idx_category 可见时,按 category 查询走索引
SELECT id, product_name, category, price
FROM t_product_index
WHERE category = '手机';

EXPLAIN 结果 ​

+----+-------------+------------------+------+---------------+--------------+---------+-------+------+----------+-------+
| id | select_type | table            | type | possible_keys | key          | key_len | ref   | rows | filtered | Extra |
+----+-------------+------------------+------+---------------+--------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t_product_index  | ref  | idx_category  | idx_category | 122     | const | 7482 |   100.00 | NULL  |
+----+-------------+------------------+------+---------------+--------------+---------+-------+------+----------+-------+
字段值分析
typeref等值匹配索引
keyidx_category正常使用分类索引
key_len122VARCHAR(30) utf8mb4 = 30×4+2
rows~7,482某分类命中约 7500 行
ExtraNULL无额外操作

为什么慢 ​

此时 idx_category 可见且被正常使用。查询本身不慢,但 DBA 怀疑这个索引已无用,想删除来节省空间和写入开销。直接 DROP INDEX 风险很大:

  1. 若有隐藏的慢查询依赖该索引,删除后会骤降为全表扫描
  2. 删除后再加回索引需要重新构建,耗时且锁表风险
  3. 生产环境很难事先穷举所有受影响的 SQL

为什么不能直接 DROP INDEX

DROP INDEX 是不可逆的破坏性操作。MySQL 5.7 及之前版本删除索引后只能重建,若发现问题需 ADD INDEX 重新构建,大表上耗时很久。

核心认知

INVISIBLE 索引让索引对优化器"隐身"但数据仍维护,实现零风险验证删除影响。确认无影响后再 DROP,有问题瞬间 VISIBLE 恢复。

优化方案 ​

good.sql ​

sql
-- good.sql: idx_category 设为 INVISIBLE 后,模拟"删除索引"的影响
-- 优化器不再使用该索引,退化为全表扫描(用于验证删除是否安全)
-- 需先执行 setup-good.sql 将索引设为不可见
SELECT id, product_name, category, price
FROM t_product_index
WHERE category = '手机';

先执行 setup-good.sql 将索引设为不可见:

sql
-- setup-good.sql: 将 idx_category 设为不可见(MySQL 8.0+)
-- 索引仍被维护,但优化器不再选用它
ALTER TABLE t_product_index ALTER INDEX idx_category INVISIBLE;

原理 ​

INVISIBLE 索引的机制:索引数据仍被持续维护(INSERT/UPDATE 仍会更新索引),但优化器在生成执行计划时忽略它,就像索引不存在一样。

安全删索引流程:
1. ALTER INDEX INVISIBLE  -> 优化器不再使用,但索引仍维护
2. 观察期 1~2 周          -> 监控慢查询日志、EXPLAIN
3. 确认无影响             -> 所有查询都走了更优的替代计划
4. DROP INDEX             -> 真正回收空间和写入开销
5. 若有问题               -> ALTER INDEX VISIBLE 瞬间恢复
操作DROP INDEXALTER INDEX INVISIBLE
索引数据立即删除保留并持续维护
优化器可见性不可用不可见(忽略)
写入开销降低(无需维护)不变(仍维护)
恢复方式ADD INDEX(重建,慢)ALTER VISIBLE(瞬间)
风险高(不可逆)低(可秒级回滚)

对比 ​

bad (索引可见)good (INVISIBLE)
typerefALL
rows~7,482~148,936
写入开销维护索引仍维护(INVISIBLE 不省写入)
指标优化前 (bad)优化后 (good)
访问类型refALL
使用索引idx_categoryNULL
扫描行数7,482148,936
附加信息NULLUsing where(模拟删除后退化)

🚀 INVISIBLE 验证删除影响,确认无问题后 DROP 才真正释放开销,有问题可瞬间恢复

避坑指南 ​

注意事项

  1. INVISIBLE 不节省写入开销。索引数据仍被维护,INSERT/UPDATE 仍更新索引。它的价值是零风险验证删除影响,只有最终 DROP INDEX 才真正释放写入开销和空间。不要把 INVISIBLE 当成"永久省空间"的手段。

  2. 5.7 不支持 INVISIBLE 索引。5.7 删索引只能直接 DROP,建议先在测试环境充分验证,或使用 pt-online-schema-change 等工具。

  3. 观察期要足够长。有些低频查询(如月报、定时任务)可能一周才跑一次,观察期至少 1~2 周,覆盖完整的业务周期。配合慢查询日志监控。

  4. 主键和唯一索引不能设为 INVISIBLE。只有普通二级索引可以设为不可见,主键和唯一约束的索引必须保持可见。

5.7 vs 8.0 差异 ​

特性5.78.0
INVISIBLE 索引❌ 不支持✅ 支持
安全删索引灰度验证❌ 只能直接 DROP✅ 先 INVISIBLE 后 DROP
索引恢复ADD INDEX(重建,慢)ALTER VISIBLE(瞬间)
SHOW INDEX 可见性无此字段Visible 字段显示 YES/NO

重要区分

INVISIBLE 索引不节省写入开销(索引数据仍被维护),它的价值是零风险验证删除影响。只有最终 DROP INDEX 才真正释放写入开销和空间。

5.7 不支持

INVISIBLE 索引是 MySQL 8.0 新特性,5.7 无此功能。5.7 删索引只能直接 DROP,建议先在测试环境充分验证,或使用 pt-online-schema-change 等工具。

本地复现 ​

bash
# 默认在 MySQL 8.0 上运行
./scripts/run-case.sh 13-invisible-index

# 在 MySQL 5.7 上运行(对比)
./scripts/run-case.sh 13-invisible-index --ver 5.7

# 跳过造数据重跑
./scripts/run-case.sh 13-invisible-index --no-seed

MIT Licensed