or条件在mysql中常导致全表扫描,应优先用union all拆分并确保各子查询有独立索引;同字段多值or改in;避免依赖不可控的index merge。

OR 条件在 MySQL 中几乎必然导致性能崩塌,除非你提前干预执行计划。它不是“写法问题”,而是优化器面对多分支逻辑时的天然短板——不拆、不改、不加索引,基本等于默认全表扫描。
EXPLAIN 看到 type=ALL 或 key=NULL 就该立刻停手
这是最直接的信号:MySQL 没打算用索引。哪怕每个 OR 分支单独执行都毫秒级,合在一起就退化。常见诱因包括:
-
OR两边字段没独立索引(比如status = 'A' OR city = 'Beijing',但只给status建了索引) - 字段名拼错(如实际是
apple_id,SQL 写成app_id,后者无索引) - 一边用了函数(
UPPER(name) = 'ALICE'),另一边是普通等值,整个条件放弃索引 -
OR和AND混用且括号缺失,导致逻辑被重解析,索引覆盖失效
优先用 UNION ALL 拆分,别信 UNION
这是最可控、见效最快的手段。每个子查询走自己的索引,合并零成本。关键点不是“能不能拆”,而是“怎么拆才不重复、不漏数据”:
- 必须用
UNION ALL,不是UNION——后者会去重,触发临时表和排序,开销可能比原OR还大 - 如果业务能保证各子查询结果互斥(比如按主键或唯一业务字段区分),直接
UNION ALL即可 - 若存在交集风险(如
status = 'active' OR age > 30可能返回同一行),第二条子查询加AND status != 'active'排除已覆盖的行 - 每个子查询必须有对应索引支撑,否则拆了也白搭;
EXPLAIN每个子句,确认key非空
同字段多值 OR 必须改 IN,且注意边界
id = 1 OR id = 2 OR id = 3 这类写法,MySQL 能识别并转为 IN,但显式写出更稳:
- 改写为
id IN (1, 2, 3),仍能走id上的索引,且优化器处理更成熟 -
IN列表长度建议控制在 500 以内;超限后部分 MySQL 版本会降级为临时表 + 文件排序 - 避免
IN (SELECT ...),子查询结果集大时极易拖垮性能;优先考虑JOIN -
IN对NULL不敏感:col IN (1, NULL)不匹配col IS NULL行,而OR可以,这点业务逻辑要核对清楚
索引合并(Index Merge)是双刃剑,别主动依赖
MySQL 确实支持 Index Merge Union,但它的触发条件苛刻且不可控:
- 要求每个
OR分支字段都有独立单列索引(INDEX(status),INDEX(city)) - 字段选择性必须高(低基数字段如
gender合并后反而更慢) - 统计信息不准时,优化器可能选错路径——预估扫描 100 行,实际扫 80 万行
- 一旦涉及范围查询(
age > 25 OR city LIKE 'Sh%'),Index Merge 基本失效
真正要命的是:你无法在 SQL 层强制启用或禁用它,只能靠 ANALYZE TABLE 更新统计信息,再赌优化器选对路。不如直接拆 UNION ALL 来得确定。











