优先用UNION ALL替代OR,确保每个分支字段均有独立索引且为等值查询;若涉及范围条件或无索引字段,需补索引或改写逻辑;务必用EXPLAIN验证key和rows是否优化。

大量 OR 条件导致全表扫描怎么办
MySQL 在遇到多个 OR 条件(尤其是跨字段或非前缀索引字段)时,往往无法有效使用索引,执行计划里常见 type: ALL 或 Extra: Using where,实际就是全表扫描。这不是配置问题,是优化器对 OR 的索引合并策略(index_merge)有严格前提:各条件字段必须各自有独立索引,且查询不能含函数、IS NULL、隐式类型转换等干扰项。
实操建议:
- 用
EXPLAIN SELECT ...看key和rows,如果key为空或rows接近表总行数,基本确认没走索引 - 检查每个
OR分支是否都满足「单字段 + 等值 + 有索引」,缺一不可;比如status = 1 OR category_id IN (2,3)中,IN本身没问题,但若category_id没索引,整个分支就失效 - 避免在
OR中混用不同字段的范围查询(如created_at > '2025-01-01' OR updated_at ),这类几乎必然退化为全表扫描
用 UNION 替代 OR 的正确写法
UNION 能让每个子查询独立走索引,但必须注意去重逻辑和执行顺序——UNION 默认去重(触发临时表 + 排序),而 UNION ALL 不去重、性能更高。只要业务能确保各子查询结果无交集(例如按不同状态值拆分),优先用 UNION ALL。
示例对比:
SELECT id, name FROM orders WHERE status = 1 OR status = 2 OR status = 3;
→ 改为:
SELECT id, name FROM orders WHERE status = 1 UNION ALL SELECT id, name FROM orders WHERE status = 2 UNION ALL SELECT id, name FROM orders WHERE status = 3;
关键点:
- 每个
SELECT必须列数、类型、顺序一致;别名只在第一个子句定义,后续不用重复 - 如果原
OR涉及不同字段(如user_id = 100 OR order_no = 'NO2025001'),需确保user_id和order_no各自建了索引,否则对应子查询仍慢 - 不要加
ORDER BY在每个子句末尾;如需全局排序,统一加在最后,并明确指定字段(如ORDER BY created_at DESC)
索引怎么配才不白建
光拆 UNION 不建对索引,等于白忙。重点不是“有没有索引”,而是“索引是否覆盖查询路径”。比如 WHERE status = 1 查询 id, name, amount,只给 status 建单列索引,仍要回表;而建联合索引 (status, id, name, amount) 就能走覆盖索引(Extra: Using index)。
建索引建议:
- 对每个
UNION子句的WHERE条件字段,优先建「等值字段前置」的联合索引,把SELECT中的其他字段追加在后(即覆盖索引) - 避免冗余索引:如果已有
(status, user_id),再单独建status单列索引意义不大 - 注意字段选择性:低区分度字段(如
gender只有男/女)不适合做联合索引首字段,除非后面紧跟高区分度字段(如(gender, create_time)在性别+时间组合查询时可能有用)
为什么有时候 UNION ALL 还比不过原 OR
这不是玄学,常见于三类情况:子查询返回数据量极大(比如某状态占全表 80%)、UNION ALL 导致多次索引扫描 + 结果集合并开销、或 MySQL 版本较老(5.6 之前对 UNION 优化弱)。此时应检查 EXPLAIN FORMAT=JSON 中的 union_result 和各 select_subquery 的 rows 估算是否严重失真。
可尝试的补救措施:
- 对高频大结果集分支,考虑用物化临时表(
CREATE TEMPORARY TABLE ... SELECT)预存,再与其他小结果集UNION ALL - 升级到 MySQL 8.0+,其对
UNION的执行计划缓存和并行扫描支持更好 - 极端场景下,改用应用层分批查询(如先查
status=1,再查status=2),由代码合并结果,反而更可控
真正卡住性能的,往往不是语法选 OR 还是 UNION,而是没看清数据分布和索引是否匹配查询模式。上线前务必用真实数据量压测,别信 EXPLAIN 的 rows 估算值。



















