同一字段多个等值OR必须改用IN,如status IN ('draft','pending','review')可稳定走range索引;不同字段OR必须拆为UNION ALL,每个分支需独立命中索引且用EXPLAIN验证;IN值不宜超500个,IN(SELECT...)不推荐,嵌套OR应先拆再JOIN。

同一字段多个等值 OR 必须改用 IN
比如 status = 'draft' OR status = 'pending' OR status = 'review',这种写法会让 MySQL 放弃索引合并(index_merge),甚至触发隐式类型转换。改成 status IN ('draft', 'pending', 'review') 后,执行计划能稳定走 range 类型,且语义更清晰。
注意几个边界点:
-
IN值数量建议控制在 500 以内;超了可能触发临时表,反而变慢 -
col IN (1, NULL)不会匹配NULL行,而col = 1 OR col IS NULL可以——行为不一致,不能无脑替换 -
IN (SELECT ...)不推荐,尤其子查询返回NULL或结果集大时,常被重写为DEPENDENT SUBQUERY,性能崩盘
不同字段的 OR 必须拆成 UNION ALL
像 user_id = 123 OR order_no = 'NO2026001' OR created_at > '2026-06-01' 这种跨字段组合,MySQL 几乎必然退化为 type: ALL 扫描。这时唯一可控的解法是手动拆分,让每个子查询独立走自己的索引。
实操要点:
- 每个子查询必须单独
EXPLAIN验证:type是ref或range,key显示命中索引,rows显著小于总行数 - 务必用
UNION ALL,不是UNION;去重开销在万级数据上就能明显拖慢 - 字段顺序、类型、可空性必须完全一致,否则报错
ERROR 1222 - 原始查询带
LIMIT或ORDER BY?不能只在外层加——得先在各子查询里加LIMIT N,再外层二次排序,否则漏数据
嵌套子查询里的 OR 几乎等于放弃索引
例如 WHERE id IN (SELECT id FROM users WHERE name = 'Alice' OR email LIKE '%@gmail.com'),MySQL 优化器很难处理子查询内的 OR,大概率出现 Using temporary; Using filesort,甚至直接全表扫描。
更稳妥的做法是把子查询先拆成 UNION ALL,再和主表 JOIN:
SELECT DISTINCT o.* FROM orders o INNER JOIN ( SELECT id FROM users WHERE name = 'Alice' UNION ALL SELECT id FROM users WHERE email LIKE '%@gmail.com' ) u ON o.user_id = u.id;
关键点:
- 每个分支都需有对应索引:
name和email各自建单列索引 -
email LIKE '%@gmail.com'仍可能走全表扫描——这时要考虑函数索引或全文索引 - 若子查询结果可能含
NULL或量很大,优先用EXISTS替代IN
为什么加了索引还是慢?重点看这些地方
很多人建了单列索引仍慢,问题不在 OR 写法,而在索引本身没对上路。MySQL 的 index_merge 触发条件极苛刻:
- 所有
OR字段必须各自有独立单列索引 - 不能含函数,如
UPPER(name) = 'ABC'或DATE(created_at) = '2026-07-01',会让索引完全失效 - 不能有前导通配符,如
LIKE '%x' - 复合索引中非前导列(如
(a, b)上查b = 1)无法被OR分支利用 -
EXPLAIN中出现Using temporary; Using filesort,基本说明GROUP BY已被迫落盘,此时光调OR没用
复杂 OR 场景下最容易被忽略的点:当混入范围查询(>、BETWEEN)、IS NULL 或模糊匹配(LIKE '%x%')时,UNION ALL 维护成本飙升,强行拆分反而更慢。这时候该考虑的是数据层重构,而不是 SQL 层微调。

















