or条件常导致type=all,因mysql优化器对or处理保守:任一分支无法走索引(如无索引、函数、隐式转换、前导通配符),整个查询即放弃索引而全表扫描。

OR 条件为什么直接触发 type=ALL
MySQL 优化器对 OR 的处理非常保守:只要任意一个分支无法走索引(比如字段没索引、用了函数、隐式转换、前导 %),整个条件就大概率放弃索引,直接 type=ALL。这不是“部分失效”,而是整体退化——哪怕 a 有索引、b 没索引,WHERE a = 1 OR b = 2 也不会让 a 单独走索引。
常见诱因包括:
-
EXPLAIN中key为空、rows接近表总行数 -
OR左右任一侧含LIKE '%x'、IS NULL、DATE(col)等干扰项 - 跨字段范围查询混用,如
created_at > '2025-01-01' OR updated_at > '2025-01-01'
用 UNION ALL 替代 OR 的硬前提
拆成 UNION ALL 不是万能解药,必须满足三个条件,否则可能更慢:
- 每个子查询单独跑
EXPLAIN SELECT * FROM t WHERE ...,确认type是ref或range,不是ALL - 业务上各子查询结果天然不重叠(例如不同状态值、互斥分类字段),才能用
UNION ALL;否则得用UNION,但会触发临时表 + 排序去重 - 原查询带
LIMIT或ORDER BY,不能只在子查询里加,必须在外层统一处理:(SELECT ...) UNION ALL (SELECT ...) ORDER BY x LIMIT 10
什么时候不该用 UNION ALL?优先建索引
如果拆完后每个子查询仍是 type=ALL,说明问题不在 OR,而在索引缺失或设计不合理。这时强行 UNION ALL 只是把一次全表扫描变成 N 次,性能反而更差。
- 单字段多值匹配(如
status = 1 OR status = 2 OR status = 3)→ 直接改用status IN (1, 2, 3),配单列索引即可 - 多个字段组合等值(如
(a = 1 AND b = 2) OR (a = 3 AND b = 4))→ 创建联合索引(a, b),利用最左前缀匹配 - 混合等值与范围(如
status = 'paid' OR amount > 100)→ 给amount单独建索引,再考虑是否拆UNION ALL
IN 和 UNION ALL 的选择边界
IN 是同一字段多值的最优解,UNION ALL 是跨字段等值的可控手段。两者不可混用:
-
WHERE id IN (1, 2, 3)→ 快,索引高效,语义清晰 -
WHERE a = 1 OR b = 2→ 不能写成IN,必须拆UNION ALL或补索引 - 误写
WHERE a IN (1, 2) OR b IN (3, 4)→ 依然可能全表扫描,因为OR两侧都含多值,优化器难合并
真正容易被忽略的是:改写后必须用 EXPLAIN 验证每个子查询是否真的走了索引——不验证,等于没优化。











