type=all 表示全表扫描,并非“简单”,而是因缺失索引被迫逐行检查;key 为 null、rows 远超预期才是关键问题;where 中使用函数(如 date(create_time))会导致索引失效,看似有索引实则未用。
explain 显示 type=all 就代表真“简单”?
不是。type=all 表示全表扫描,恰恰说明数据库找不到可用索引,被迫逐行检查——这在百万级表上可能扫几秒,但执行计划本身却显得“干净”,容易误判为“没问题”。真正的问题藏在 key 为 null、rows 远超预期这些字段里。比如:explain select * from orders where status = 'paid' 返回 type=all,哪怕 status 字段只占 5% 数据,只要没索引,优化器就放弃走索引路径。
WHERE 条件里带函数,执行计划会“假装正常”
你写 WHERE DATE(create_time) = '2025-01-01',EXPLAIN 可能仍显示 type=ref 或 key 有值,但实际没走索引。因为函数调用让索引失效,优化器有时会“降级”选择一个次优路径(比如用主键索引 + 过滤),导致 rows 值虚低、Extra 出现 Using where。这种“看似有索引、实则白搭”的情况,比全表扫描更难察觉。
- 改写为范围查询:
create_time >= '2025-01-01' AND create_time - 对常用日期格式建生成列索引(MySQL 5.7+):
ALTER TABLE orders ADD COLUMN create_date DATE AS (DATE(create_time)) STORED,再给create_date加索引
SELECT * 让执行计划“隐藏成本”
SELECT * 不会直接让 EXPLAIN 输出变复杂,但它会导致两个关键问题:一是无法触发覆盖索引,强制回表;二是如果表里有大字段(如 TEXT、BLOB),即使只查 1 行,rows 值不变,I/O 和内存开销却飙升。执行计划里看不出字段体积,Extra 也不会报错,只有 SHOW PROFILE 或慢日志里的 Rows_examined 和 Bytes_sent 才暴露真实代价。
- 用
EXPLAIN FORMAT=JSON查看used_columns,确认是否真的需要所有列 - 对比
SELECT id, name FROM users WHERE ...和SELECT * FROM users WHERE ...的rows是否一致——若一致但耗时翻倍,大概率是回表或大字段拖累
统计信息过期会让执行计划“突然变坏”
一张表昨天只有 1 万行,今天批量导入 200 万行,但 ANALYZE TABLE 没跑过。优化器还按旧的分布估算,可能选错连接顺序、误判索引收益,甚至把本该走索引的查询硬切到全表扫描。这种变化不会体现在 SQL 写法里,EXPLAIN 看起来也“合理”,但实际执行时卡住。尤其在分区表或大宽表上,统计偏差影响极深。
- 定期执行
ANALYZE TABLE(MySQL)或VACUUM ANALYZE(PostgreSQL) - 对高频查询的 WHERE 字段,用
SELECT COUNT(*) GROUP BY快速验证数据倾斜程度 - 避免在 OLTP 场景中依赖自动统计更新,它往往滞后且采样不准
执行计划“看起来简单”,往往是因为它没告诉你磁盘读了多少次、内存用了多少、网络传了多少字节——这些才是让简单查询变慢的真实原因。











