全表扫描最直接的信号是type字段值为all;rows异常高、key为null、extra含using where/using filesort/using temporary,以及type为index但区分度低导致回表,均提示潜在全表扫描。

Explain 输出中哪些字段暴露了全表扫描
全表扫描最直接的信号是 type 字段值为 ALL,这表示 MySQL 正在遍历整张表的每一行。但别只盯着这一项——rows 值异常高(比如远超实际结果集数量)、key 为 NULL(本该用索引却没用上),以及 Extra 中出现 Using where; Using filesort 或 Using temporary,往往和全表扫描互为因果。
特别注意:即使 type 是 index(索引全扫描),如果扫描的是二级索引且覆盖不了查询字段,MySQL 仍可能回表再查主键,等效于“伪全表扫描”,尤其当该索引区分度极低时(如 status 只有 0/1)。
为什么加了索引 Explain 还显示 ALL
常见原因不是索引没建,而是索引没被选中。典型场景包括:
-
WHERE条件对字段用了函数或表达式,比如WHERE YEAR(create_time) = 2023—— 索引失效,强制全表扫描 - 隐式类型转换,比如
user_id是BIGINT,但查询写成WHERE user_id = '123',字符串与数字比较触发转换,索引失效 - 联合索引顺序错位,比如建了
(a, b, c),但查询只用了b = ? AND c = ?,跳过最左前缀,索引无法命中 - 优化器估算走索引比全表扫描更慢(例如表很小、或索引选择性差),主动放弃索引——这时可加
FORCE INDEX验证,但慎用
用 Explain 定位慢查询的真实瓶颈点
别只看单条 SQL 的 EXPLAIN,要结合执行计划和真实数据分布交叉验证:
- 对比
rows预估和SELECT COUNT(*)实际行数,若相差 5 倍以上,说明统计信息陈旧,运行ANALYZE TABLE更新 - 检查
Extra中的Using index condition(ICP)是否生效:它表示下推条件到存储引擎层过滤,能大幅减少回表次数;若没出现,可能是 MySQL 版本太低( - 对
JOIN查询,按id顺序逐行看驱动表是否合理——小表做驱动表能显著降低关联开销;若大表被先扫,type=ALL就成了放大器
示例:执行 EXPLAIN SELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE u.status = 1;,若 users 表没在 status 上建索引,哪怕 orders 有索引也会因驱动表全扫而拖垮整体性能。
优化后如何确认全表扫描真正消失
改完索引或重写 SQL 后,不能只信 EXPLAIN 的 type 变成了 ref 或 range,还要验证两点:
- 真实执行时是否真的走了新索引:开启
slow_query_log并设置long_query_time=0,抓取实际慢日志,看Rows_examined是否大幅下降 - 并发压力下的稳定性:单次
EXPLAIN不反映锁竞争,用SHOW PROFILE FOR QUERY查看Handler_read_next和Handler_read_rnd_next指标,前者飙升说明索引扫描频繁,后者高则意味着大量随机回表 - 避免“假优化”:比如把
SELECT *改成SELECT id后type变好看了,但业务逻辑其实需要其他字段——这种优化没意义,得补全覆盖索引
真正棘手的从来不是加索引本身,而是判断“这个字段值分布是否值得建索引”“这个联合索引字段顺序是否贴合所有高频查询模式”——这些没法靠 EXPLAIN 直接回答,得看 SELECT COUNT(DISTINCT ...) 和慢日志里的真实谓词组合。










