or条件易致全表扫描,因破坏b+树有序路径;优化宜用union all拆分并确保各子查询走索引,或改用in、between等替代,必要时建函数索引,并通过explain验证执行计划。

OR条件为什么容易导致全表扫描
MySQL在遇到 WHERE a = 1 OR b = 2 这类多字段OR查询时,除非两个字段都有独立索引且优化器选择Index Merge,否则大概率放弃走索引,直接全表扫描。这是因为OR破坏了B+树索引的有序遍历路径——索引只能高效支持“单路径连续查找”,而OR相当于要求同时走两条不相交的路径。
常见错误现象:EXPLAIN 显示 type: ALL、key: NULL、Extra 里没有 Using union;CPU飙升、慢查询日志频繁出现该SQL。
- 即使
a字段有索引,只要b没索引,整个OR条件基本失效 - 字符串字段用
OR+LIKE '%xxx',索引必然失效 - OR中混用
IS NULL或函数(如DATE(created_at) = '2024-01-01'),同样触发全表扫描
UNION ALL 替代 OR 的实操要点
把一个含OR的查询拆成多个独立子查询,再用 UNION ALL 合并,是见效最快的方式。前提是每个子查询都能单独命中索引。
例如原语句:SELECT * FROM users WHERE name = 'Alice' OR city = 'Beijing';
优化后写法:
SELECT * FROM users WHERE name = 'Alice' UNION ALL SELECT * FROM users WHERE city = 'Beijing';
- 必须用
UNION ALL而非UNION,避免去重开销(除非业务逻辑真需要去重) - 每个子查询的
WHERE条件字段必须有对应索引,否则只是把全表扫描拆成两次 - 若结果需去重且数据量不大,可加
DISTINCT在外层包裹,但优先检查是否真有必要 - 注意:
ORDER BY和LIMIT要放在整个UNION ALL之后,不能写在子查询里
哪些情况不适合直接拆UNION
不是所有OR都适合硬拆。以下场景改用其他方式更稳妥:
- 同一字段多值匹配,比如
status = 'A' OR status = 'B' OR status = 'C'→ 改用status IN ('A','B','C'),语义清晰且MySQL能用索引 - 范围条件组合,如
amount 1000→ 改用amount NOT BETWEEN 100 AND 1000,有时执行计划更优 - 涉及
NULL判断,如email = 'a@b.com' OR email IS NULL→ 单独建函数索引(MySQL 8.0+)或增加计算列索引,硬拆会导致IS NULL部分无法走索引 - OR连接的条件存在强相关性(如
tenant_id = 123 AND (status = 'active' OR created_at > NOW() - INTERVAL 7 DAY))→ 先确保tenant_id是复合索引首列,再考虑覆盖其他字段
验证索引是否真正生效
写了UNION、加了索引,不代表就优化完成了。必须用 EXPLAIN 看真实执行路径:
- 每个
SELECT子句的type应为ref、range或const,绝不能是ALL -
key列要显示具体索引名,而不是NULL - 如果看到
Using temporary或Using filesort,说明合并结果时排序/去重代价高,需检查是否真需要*或是否漏了ORDER BY字段的索引 - 对大表,用
EXPLAIN FORMAT=JSON查看rows_examined_per_scan,确认扫描行数是否显著下降
最常被忽略的一点:UNION后结果集的排序和分页(如 ORDER BY created_at DESC LIMIT 20)会强制MySQL先合并全部结果再排序,可能抵消索引优势。此时应考虑用延迟关联或游标分页替代。











