or导致索引失效,因b+树索引适合and收敛查询,而or需合并多个不连续扫描结果,优化器难以预估代价,常退化为全表扫描;union all可拆解并让各分支独立走索引,但需确保每分支条件互斥、列一致且无隐式转换。

为什么OR会让索引失效
当WHERE子句中出现多个OR连接的条件(比如WHERE status = 'A' OR status = 'B' OR type = 'X'),优化器常放弃使用索引,尤其在涉及不同列或范围条件时。MySQL 5.6+、PostgreSQL 对简单单列等值OR有一定优化能力,但一旦混入IS NULL、函数、或跨列组合,就会退化为全表扫描。
根本原因在于:B+树索引天然适合“AND”路径下收敛查询范围,而OR要求合并多个独立扫描结果,优化器难以预估各分支的代价与交集大小,干脆选更保守的执行计划。
UNION ALL比UNION更适合改写OR查询
UNION会自动去重并排序,带来额外开销;而绝大多数OR等价场景中,各分支条件互斥(如status = 'A'和status = 'B'不可能同时命中同一行),用UNION ALL更准确、更快。
- 确保每个
SELECT子句都能命中索引——单独验证每个分支的EXPLAIN输出,确认type是ref或const,而非ALL - 所有
SELECT必须返回相同数量、顺序和类型的列,否则报错ERROR 1222 (21000): The used SELECT statements have a different number of columns - 如果原查询有
ORDER BY或LIMIT,只能加在最外层,不能放在每个UNION ALL子句里
示例改写:
SELECT id, name FROM users WHERE status = 'active' OR status = 'pending'; → SELECT id, name FROM users WHERE status = 'active' UNION ALL SELECT id, name FROM users WHERE status = 'pending';
什么时候UNION ALL也救不了OR?
不是所有OR都适合拆——如果条件之间存在逻辑重叠(比如id > 100 OR created_at > '2023-01-01'),UNION ALL会产生重复行,而UNION又慢且难优化。此时应优先考虑:
- 检查是否真需要
OR:能否用IN替代单列多值(status IN ('A','B')),它通常能走索引 - 是否存在覆盖索引可能:把查询字段加入联合索引,避免回表放大
OR的代价 - 是否可改用
INFORMATION_SCHEMA或物化临时表预过滤,特别是当OR右侧是子查询结果时
另外,PostgreSQL 中可尝试SET enable_seqscan = off强制走索引测试,但生产环境慎用;MySQL 则可通过FORCE INDEX提示指定索引,但无法绕过OR本身的执行路径限制。
别忽略统计信息和隐式类型转换
即使用了UNION ALL,如果某一分支因隐式类型转换失效索引(例如WHERE phone = 13800138000,而phone是VARCHAR),该分支仍会全表扫描,拖累整体性能。
- 用
SHOW WARNINGS检查是否有Truncated incorrect DOUBLE value类提示 - 执行
ANALYZE TABLE更新统计信息,避免优化器误判各分支结果集大小 - 对
UNION ALL后的外层查询加LIMIT时,注意MySQL 8.0.20+才支持下推优化,旧版本仍会先合并全部结果再截断
真正卡住性能的,往往不是OR本身,而是改写后某个分支悄悄退化成了全表扫描,却因为UNION ALL掩盖了执行计划差异。










