
OR 条件在 MySQL 中极易导致索引失效,不是写法错了,是优化器根本没法高效走索引 —— 除非所有 OR 分支都严格命中同一复合索引的最左前缀,且无函数、无类型转换、无 NULL 混合。真实场景里这条件太苛刻,所以别赌运气,直接用 UNION ALL 拆。
为什么 OR 常常不走索引?
MySQL 优化器对 OR 的处理逻辑本质是“难以合并多个索引扫描路径”。哪怕 a = 1 OR b = 2 中 a 和 b 各有独立索引,优化器也大概率放弃索引、全表扫描(type: ALL)。只有在 MySQL 8.0+ 且满足严格条件时,才可能触发 index_merge;但该策略仍需回表、去重、排序开销,性能不稳定。
常见错误现象:
EXPLAIN 显示 key 为空、type 为 ALL 或 range 但 rows 接近总行数查询耗时随数据量非线性增长(比如从 10 万到 100 万行,耗时翻 5 倍以上)加了索引也没用,OR 一出现,索引就“隐身”
UNION ALL 替代 OR 的实操要点
把一个含 OR 的查询,拆成多个子查询,各自走自己的索引,再用 UNION ALL 合并结果 —— 这是最可控、效果最稳的方案。
必须用 UNION ALL,不是 UNION:后者会强制去重 + 排序,额外消耗 CPU 和临时磁盘,尤其大数据量时明显拖慢每个子查询要能独立走索引:确保 WHERE 子句中只保留该分支对应字段的等值条件(如 name = ‘Alice’),不要混入其他过滤逻辑若原查询有 ORDER BY … LIMIT,不能只在外层加:应先在每个子查询里加 LIMIT(按需估算上界),再外层统一排序分页,否则可能漏数据字段顺序和类型必须完全一致:列数、名称(或用别名对齐)、数据类型要兼容,否则报错 UNION types not compatible
示例:
SELECT * FROM users WHERE name = ‘Alice’ OR city = ‘Beijing’;→ 改为:(SELECT * FROM users WHERE name = ‘Alice’)UNION ALL(SELECT * FROM users WHERE city = ‘Beijing’);
IN 和 OR 的边界在哪?什么时候不该换 UNION ALL?
当 OR 全部作用于**同一字段的多个等值判断**时,IN 是更简洁、更安全的选择,MySQL 对其索引优化成熟稳定。
适用:status = 1 OR status = 2 OR status = 3 → 直接改 status IN (1, 2, 3),仍走索引不适用:name = ‘Alice’ OR email = ‘alice@example.com’ → 两个字段,必须拆 UNION ALL注意 IN 对 NULL 的特殊行为:col IN (1, NULL) 永远不匹配 NULL 行,而 col = 1 OR col IS NULL 会匹配 —— 语义不同,不能无脑替换
容易被忽略的验证动作
改完不是就结束了。真正关键的是用 EXPLAIN 对比执行计划:
原 OR 查询:确认是否出现 type: ALL 或 key: NULL新 UNION ALL 查询:分别对每个子查询 EXPLAIN,看 key 是否命中预期索引、rows 是否显著下降外层包装后整体 EXPLAIN:关注是否出现 Using temporary 或 Using filesort —— 如果有,说明 ORDER BY / LIMIT 位置不合理,得调整子查询内部的排序和截断
索引本身也要配合:如果拆出来的字段没索引,UNION ALL 也白搭。先确保 name 有 idx_name,city 有 idx_city,再动手改 SQL。

评论(0)