key为空但possible_keys有值,说明索引被优化器看见却未选用,主因是最左前缀不匹配、函数操作、隐式类型转换、统计信息过期或选择性过低;若possible_keys也为空,则极可能索引根本未创建。

Key 为空,不等于没建索引,也不等于索引坏了——大概率是 MySQL 优化器主动跳过了它,或者索引压根就不存在。
possible_keys 有值但 key 为空,说明索引被“看见”却没被选中
这是最典型也最容易误判的情况。优化器在 possible_keys 里列出了候选索引,但最终决定不用,key 就显示为空。常见真实原因包括:
- WHERE 条件不满足最左前缀:比如索引是
(status, category, created_at),但查询只写了WHERE category = 'A',这个索引完全不可用 - 对索引字段用了函数:
WHERE DATE(create_time) = '2025-01-01'→ 即使create_time有索引也失效 - 隐式类型转换:
user_id是BIGINT,却写成WHERE user_id = '123'(字符串字面量),MySQL 放弃索引做全表扫描 - 统计信息过期:大批量写入后没运行
ANALYZE TABLE table_name,优化器误判“回表代价 > 全表扫描” - 索引选择性太低:比如
status只有'active'/'inactive'两个值,优化器认为走索引反而更慢
key 和 possible_keys 都为空,极可能是索引根本没建
别急着调优 SQL,先确认索引是否存在。Navicat 还原数据库时默认不带索引,尤其是 .nb3 备份文件,还原后表结构里压根没有 KEY 定义。
- 运行
SHOW INDEX FROM table_name,输出为空或只有PRIMARY,说明索引缺失 - 打开 .sql 备份文件,全文搜索
CREATE INDEX或KEY,看是否真包含该语句 - 如果是 .nb3 格式备份,它不保存完整 DDL,必须手动补建:
CREATE INDEX idx_user_status ON users(status) USING BTREE - Navicat 图形界面点选建索引容易漏写
USING BTREE、列名大小写不一致,手写更稳
JSON 字段查询 key 为空,大概率是虚拟列或函数索引配置错
MySQL 8.0.22 前不支持直接对 JSON_EXTRACT() 建函数索引,必须用虚拟列。而虚拟列类型(VIRTUAL / STORED)和索引必须严格匹配。
- 虚拟列定义用了
VIRTUAL,但索引建在了STORED列上(或反过来),优化器直接忽略 - WHERE 写的是
params->'$.item_id' = 123,但虚拟列表达式是json_extract(`params`, '$.item_id')—— 二者语法等价,但内部 token 不同,老版本不视为可复用 - 混用
->和JSON_EXTRACT(),优化器无法统一路径推导 - 检查
SHOW CREATE TABLE table_name,确认虚拟列存在且类型正确,再确认该列出现在SHOW INDEXES的Column_name中
FORCE INDEX 能验证索引是否可用,但不是解法
临时加 FORCE INDEX 是为了快速确认“是不是索引本身能用”,不是长期方案。
- 执行
SELECT * FROM orders FORCE INDEX(idx_user_id) WHERE user_id = 123,若rows明显下降、耗时缩短,说明索引结构没问题 - 但如果
idx_user_id字段类型和WHERE中的值不一致(如传字符串),FORCE INDEX仍不会生效 - PostgreSQL 不支持
FORCE INDEX,得用SET enable_seqscan = off或/*+ IndexScan(t idx_name) */Hint - 强制后有效,下一步该更新统计信息或修正查询写法,而不是继续依赖
FORCE
最常被忽略的一点:Navicat 执行计划表格是渲染结果,不是权威来源。真正要确认索引是否生效,必须看 EXPLAIN FORMAT=TRADITIONAL 或 EXPLAIN FORMAT=JSON 的原始输出,尤其盯住 key 和 possible_keys 字段值,再交叉验证 SHOW INDEX 和 SHOW CREATE TABLE。


















