type=all表示mysql放弃所有索引、逐行全表扫描,必须立即优化;应先验证各or分支是否独立走索引,再考虑union all拆分或in优化,且order by/limit须置于union all外层。

EXPLAIN 里看到 type=ALL 就该立刻停手
这不是“可能慢”,而是 MySQL 明确告诉你:它放弃了所有索引,正在逐行扫描整张表。尤其当 WHERE 中出现 OR,且分支涉及不同字段(比如 status = 'paid' OR user_id = 123)时,type=ALL 几乎是标配。此时别急着加索引或改 SQL,先验证每条分支单独执行是否真能走索引:
- 分别运行
EXPLAIN SELECT * FROM t WHERE status = 'paid';和EXPLAIN SELECT * FROM t WHERE user_id = 123; - 如果其中任意一个返回
key=NULL或type=ALL,整个OR查询就不可能高效——优化器不会为部分失效的条件冒险启用 index_merge - 注意:哪怕
user_id有索引,但写成user_id + 0 = 123或传入字符串'123'(而字段是INT),也会触发隐式转换导致该分支索引失效
用 UNION ALL 替代 OR 前必须满足三个硬条件
这不是语法替换,而是把“让优化器猜”变成“你指定路径”。但前提是:
- 每个子查询的
WHERE条件字段,必须有对应可用索引(单列或复合索引的最左前缀) - 分支数量建议 ≤ 5~10 个;超过这个数,MySQL 5.7+ 的执行计划可能退化,8.0 虽支持更多,但 planner 开销会上升
- 结果集天然不重叠,或业务允许重复——否则加
UNION会引入临时表 + 排序,开销可能反超原OR
例如原语句:SELECT id, name FROM users WHERE status = 'active' OR level > 10;
优化后应写成:
SELECT id, name FROM users WHERE status = 'active' UNION ALL SELECT id, name FROM users WHERE level > 10 AND status != 'active';
第二条里的 AND status != 'active' 是可选的去重逻辑,若表中 status 和 level 天然正交(比如状态和等级无业务重叠),可直接去掉,提升速度。
IN 替代同字段 OR 才是零成本首选
如果 OR 全部作用于同一字段,比如 id = 1 OR id = 2 OR id = 3 OR id = 4,别碰 UNION ALL,直接改 IN:
-
WHERE id IN (1, 2, 3, 4)语义等价、写法简洁,MySQL 会自动走range类型索引访问 - 值数量控制在 500 以内;超量时某些版本(如 5.7)可能触发文件排序,反而变慢
- 注意行为差异:
col IN (1, NULL)不会匹配NULL行,而col = 1 OR col IS NULL可以——别盲目替换
别信“MySQL 8.0 支持 index_merge 就不用动”的说法
MySQL 8.0 确实增强了索引合并能力,但它生效极其苛刻:
- 所有
OR分支必须是独立等值条件(不能含LIKE '%x'、IS NULL、函数如DATE(created_at)) - 每个字段必须有可用单列索引,且不能因类型隐式转换失效
- 一旦任一分支不满足,优化器仍会回退到
type=ALL,而不是“部分合并”
所以,真正可控的方式还是手动拆成 UNION ALL 并逐条验证 EXPLAIN。最常被忽略的一点是:ORDER BY 和 LIMIT 必须放在整个 UNION ALL 之后,写在子查询里不仅无效,还可能误导执行计划。











