MySQL 8.0不可见索引默认不参与执行计划,必须显式开启use_invisible_indexes开关才能被优化器识别;其设计初衷是安全验证索引删除影响,而非提升查询性能。

不可见索引默认不参与执行计划,必须显式开启开关
MySQL 8.0 默认关闭 use_invisible_indexes,哪怕你已经执行了 ALTER TABLE t ALTER INDEX idx_name INVISIBLE,优化器也完全无视它——EXPLAIN 中不会出现该索引名,FORCE INDEX(idx_name) 会直接报错 ERROR 1176 (HY000): Key 'idx_name' doesn't exist in table 't'。这不是配置遗漏,是设计如此。
要让它“被看见”,必须在当前会话中手动开启:
SET SESSION optimizer_switch = 'use_invisible_indexes=on';
注意:SET GLOBAL 全局开启不推荐,生产环境易引发不可控行为;该设置只对当前连接生效,新开连接需重新设置。
验证时必须用干净会话 + 完全一致的 SQL
很多人在同一个会话里先开开关、再改索引可见性,结果对比失真。原因有二:旧会话可能残留 optimizer_switch 状态;查询缓存或预编译计划未刷新。
正确做法是:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 用全新连接(例如
mysql -u root -p --defaults-file=/dev/null)避免继承任何 session 状态 - 确保两次
EXPLAIN FORMAT=TREE使用**完全相同的 SQL 文本**(包括空格、注释、参数占位符) - 不要依赖
EXPLAIN的key字段判断——它对 invisible 索引始终为空;要看used_key或执行路径中的实际访问方式 - 配合
EXPLAIN ANALYZE观察真实执行耗时和扫描行数,比纯预估更可靠
FORCE INDEX 会绕过 invisible 限制,这是重大干扰项
FORCE INDEX 对不可见索引完全无效——只要语法合法,它就会强制走该索引,不管 Visible: NO 还是 optimizer_switch 开关状态。这意味着:
- ORM 框架如 Django 的
.extra(index='idx_name')、MyBatis 的都可能绕过隐藏逻辑USE INDEX(idx_name) - 灰度前必须全局
grep -r "FORCE INDEX\|USE INDEX\|IGNORE INDEX" .扫描代码库 - 若线上 SQL 实际带 hint,那“无感知验证”就失去意义——你测的不是优化器选择逻辑,而是人工干预结果
写入开销一分不少,别误以为“看不见=没代价”
不可见索引仍是物理存在的 B+ 树结构。所有 INSERT/UPDATE/DELETE 操作仍会同步更新它,innodb_rows_inserted 统计值照常增长。如果你发现某索引写入压力大但查询几乎不用,隐藏它只是掩盖问题;真正降负载得 DROP INDEX。
对比测试时容易忽略的一点:只比查询 QPS 是片面的。务必同步压测写入吞吐,比如用 Sysbench 的 oltp_write_only 场景,否则你会低估它的副作用。
最隐蔽的风险是备份恢复后行为突变:mysqldump --no-data 不导出 INVISIBLE 关键字,还原后索引自动变 VISIBLE;某些 ORM 在读取 SHOW INDEX 元数据时跳过 Visible: NO 的索引,导致迁移脚本漏处理——这些都不是执行计划层面的问题,但上线后立刻暴露。










