确诊未走索引:type=all 或 key=null 表明优化器放弃所有索引,优先检查 rows 是否接近总行数、回表开销、order by 列是否在索引中、cardinality 是否失真、是否存在隐式转换或函数操作,以及 key_len 是否异常截断。

EXPLAIN 显示 type=ALL 或 key=NULL 就是确诊了
这不是警告,是优化器明确告诉你:这次查询没走你建的任何索引。type=ALL 表示它决定顺序扫描聚簇索引全部叶子节点;key=NULL 表示所有二级索引都被评估后主动弃用。别急着加索引,先看 EXPLAIN 输出里 rows 是不是接近表总行数——如果是,说明优化器算过账,觉得走索引更慢。
回表开销大时,PRIMARY 反而更便宜
二级索引叶子节点只存索引列 + 主键,SELECT * 必须回表取整行。每次回表都是随机 I/O,跳页成本高。如果优化器预估要回表几十甚至上百次,总成本很容易超过顺序读几页聚簇索引。
- 你的联合索引是
idx_source_id(source_id,source_type,state),但查询带ORDER BY id ASC LIMIT 1→id不在索引中,无法避免排序或额外比对 -
ANALYZE TABLE后再看EXPLAIN FORMAT=JSON里的query_cost,对比走PRIMARY和走idx_source_id的数值差异 - 覆盖索引能彻底规避回表,比如把
SELECT *改成SELECT source_id, source_type, state,再确认是否触发Using index
Cardinality 失真会让优化器误判索引价值
Cardinality 是优化器估算成本的关键输入,但它只是采样估算值,不是精确统计。大批量导入后长期没更新、数据倾斜严重(比如 status 只有 3 个值),都可能导致 Cardinality 严重偏低——优化器一看“这索引区分度太差”,直接放弃。
- 查当前基数:
SHOW INDEX FROM t WHERE Key_name = 'idx_status',重点看Cardinality是否明显低于实际唯一值数量 - 强制刷新:
ANALYZE TABLE t,注意该操作会锁表,生产环境避开高峰 - 长期方案:设
innodb_stats_persistent=ON+innodb_stats_auto_recalc=ON,让 MySQL 自动维护
隐式转换和函数操作直接踢出候选索引列表
优化器在第二步「候选索引筛选」就排除了这类索引——根本不会进入成本计算环节。哪怕索引结构再完美,只要 WHERE 条件里对索引列做了函数、运算或类型不匹配的比较,它就自动出局。
-
status是VARCHAR,但写成WHERE status = 1→ 触发隐式转换,索引失效 -
create_time是DATETIME,但写成WHERE YEAR(create_time) = 2023→ 函数破坏有序性 - 用
SHOW CREATE TABLE核对字段真实类型,再比对 SQL 中字面量类型,字符串必须加引号
rows 和 key_len 共同暴露的“假索引”:看起来走了索引(type=ref),但 key_len 比预期小很多,说明中间某列因范围查询或类型不匹配被截断了,实际过滤能力极弱。











