MySQL中OR条件易变慢是因为5.7及更早版本无法同时使用多个单列索引,常导致全表扫描;8.0虽支持索引合并但限制严格,实际仍可能退化为全表扫描,而UNION ALL替代方案仅在各分支能独立走索引、结果不重叠且数量≤5~10时有效。

MySQL 中 OR 条件为什么容易变慢
当 OR 连接的多个条件涉及不同字段(比如 htid = ? OR raid = ?),且这些字段各自有单列索引但没有联合索引时,MySQL 5.7 及更早版本通常无法同时利用多个索引,最终退化为全表扫描。即使字段上有索引,优化器也大概率放弃使用——因为 OR 的语义让索引范围难以合并。
MySQL 8.0 引入了索引合并(Index Merge),理论上能缓解这个问题,但实际生效有严格前提:各条件必须走**独立可用的索引**,且不能含函数、类型隐式转换、NULL 判断等干扰项。一旦不满足,照样回退到全表扫描。
更麻烦的是,当 OR 出现在大表(千万级+)且无覆盖索引的查询中,执行计划里常看到 type: ALL 和极高的 rows 预估,这是性能崩塌的明确信号。
用 UNION ALL 替代 OR 的适用场景
这个替换不是万能的,只在以下情况真正有效:
- 每个
OR分支都能独立命中某个索引(例如WHERE a = 1 OR b = 2,且a和b各自有单列索引) - 分支之间结果集基本不重叠(避免后续去重开销),或业务允许重复(
UNION ALL不去重) - 分支数量不多(建议 ≤ 5~10 个),否则
UNION ALL的执行计划会变复杂,优化器可能拒绝使用索引 - 你控制得了 SQL 拼装逻辑(比如应用层循环生成多个子查询),而不是硬写死在视图或存储过程中
注意:UNION(带去重)会额外触发临时表和排序,比 UNION ALL 慢得多,除非业务强要求去重,否则一律用 UNION ALL。
实际改写示例与关键细节
原始低效语句:
SELECT htid, raid, SUM(amount) FROM configuration WHERE (htid = 'x1' AND raid = 'y1') OR (htid = 'x2' AND raid = 'y2') OR (htid = 'x3' AND raid = 'y3') AND date BETWEEN '2025-01-01' AND '2025-12-31' GROUP BY htid, raid;
改写为 UNION ALL:
SELECT htid, raid, SUM(amount) FROM configuration WHERE htid = 'x1' AND raid = 'y1' AND date BETWEEN '2025-01-01' AND '2025-12-31' GROUP BY htid, raid UNION ALL SELECT htid, raid, SUM(amount) FROM configuration WHERE htid = 'x2' AND raid = 'y2' AND date BETWEEN '2025-01-01' AND '2025-12-31' GROUP BY htid, raid UNION ALL SELECT htid, raid, SUM(amount) FROM configuration WHERE htid = 'x3' AND raid = 'y3' AND date BETWEEN '2025-01-01' AND '2025-12-31' GROUP BY htid, raid;
关键点:
- 每个子查询都保留了
date条件,确保能走idx_date或联合索引(如已有idx_ht_ra_date) - 没用
UNION,避免隐式去重;如果业务真要唯一结果,应在应用层处理,或最后套一层SELECT DISTINCT - 若原
OR条件是“同字段多值”,比如htid IN ('x1','x2','x3'),优先用IN而不是拆成UNION ALL——IN在索引存在时效率更高,且可被优化器转为 range 访问
比 UNION ALL 更稳的替代方案
当 OR 条件超过 10 组、或字段组合复杂时,UNION ALL 的维护成本和执行计划不确定性会上升。这时更推荐:
- 建联合索引:比如上面例子中,
htid和raid总是一起出现,直接建INDEX idx_ht_raid_date (htid, raid, date),一条索引覆盖全部条件 - 用临时表预存 ID:把所有匹配的主键先查出来插入临时表,再用
JOIN回原表——适合条件动态生成、且 ID 数量可控(几万以内) - 应用层分批查询:把 1000 对
(htid, raid)拆成每批 50 组,发多次查询并合并结果,比单条巨长UNION ALL更易监控和超时控制
真正容易被忽略的是:索引是否覆盖了 GROUP BY 和 SELECT 字段。如果 SUM(amount) 没被索引覆盖,即使 WHERE 部分走索引,最终仍要回表读取整行——这时候加一个覆盖索引(如 (htid, raid, date, amount))比折腾 UNION ALL 更治本。


















