EXPLAIN显示type=ALL表示全表扫描,响应慢,主因是缺索引、函数操作或隐式转换;应结合rows和filtered评估、按最左前缀建联合索引、优先等值条件、善用覆盖索引并定期更新统计信息。
EXPLAIN 显示 type=ALL 就该警惕这表示 MySQL 正在执行全表扫描,即逐行读取整张表来匹配条件。哪怕只有几万行,一旦并发上来或字段含大文本,响应就会明显变慢。常见诱因是 WHERE 条件列没索引、用了函数(如 WHERE YEAR(created_at) = 2025)、或隐式类型转换(比如对字符串字段传数字值)。
EXPLAIN FORMAT=TREE 查看更直观的执行路径,注意 rows 和 filtered 的乘积是否远超实际返回行数!=、NOT IN、LIKE '%abc' —— 这些基本无法走索引(a, b, c),那 WHERE b = ? 或 WHERE c = ? 是不会命中索引的单列索引适合高频独立查询的字段,比如 user_id、status;联合索引则要按「查询频率高 + 过滤性强 + 出现在 WHERE 最左侧」的顺序排列。MySQL 8.0+ 支持降序索引,但 ORDER BY a ASC, b DESC 需显式声明 (a ASC, b DESC) 才能完全覆盖排序。
>, BETWEEN)放后面,ORDER BY 列尽量接在最后SELECT * FROM orders WHERE shop_id = ? AND status = ? ORDER BY created_at DESC,推荐索引为 (shop_id, status, created_at)
WHERE 字段都建索引——索引越多,写入越慢,且优化器可能选错执行计划SELECT * 和覆盖索引的关系如果一个查询的所有字段都能从索引中直接获取(即「覆盖索引」),MySQL 就不必回表查聚簇索引,性能提升显著。但前提是这些字段必须全部出现在同一个索引的「叶子节点」里。
(user_id, email, created_at),那么 SELECT user_id, email FROM users WHERE user_id = ? 就是覆盖索引查询SELECT * 几乎不可能被覆盖,因为主键以外的字段(尤其是 TEXT、BLOB)不会存进二级索引EXPLAIN 看 Extra 列是否含 Using index,有就是覆盖了;若出现 Using index condition,说明用了 ICP(索引条件下推),也比全表扫描强即使建了索引,MySQL 也可能不走它。典型情况包括:统计信息过期、索引选择性太低(比如 gender 只有 'M'/'F')、查询返回大量行(优化器认为全表扫描反而更快)、或者存在更优的索引被误选。
ANALYZE TABLE table_name 更新统计信息,尤其在大批量导入后WHERE status = 'processing' AND updated_at > NOW() -
INTERVAL 1 HOUR)FORCE INDEX 是临时手段,不能替代问题定位;真正要查的是为什么优化器没选对——通常得结合 SHOW INDEX 和 EXPLAIN 对比评估EXPLAIN SELECT id, name FROM users WHERE deleted = 0 AND created_at > '2025-01-01';
索引设计不是一劳永逸的事。表结构变、数据分布变、查询模式变,原来高效的索引可能变成瓶颈。重点不是堆索引,而是理解每条慢查询背后的访问路径和数据特征。