OR使复合索引失效,因优化器无法保证各分支共用最左前缀;应改用UNION ALL并为每个字段建单列索引,配合EXPLAIN验证执行计划。

OR 为什么让复合索引失效
MySQL 优化器在遇到 OR 时,很难判断多个分支是否能共用同一复合索引的最左前缀。哪怕你建了 INDEX(a, b, c),写成 WHERE a = 1 OR b = 2,优化器大概率放弃走这个索引,直接全表扫描——不是语法错,是执行计划层面就放弃了。
根本原因是:复合索引依赖字段的有序性,而 OR 把逻辑拆成并行路径,优化器无法保证每个分支都落在索引的连续区间里。尤其当 OR 涉及不同字段、或其中某个字段没索引时,整个 WHERE 就退化。
什么时候必须用 UNION ALL 替代 OR
满足以下任一条件,就该立刻考虑改写:
-
OR连接的是不同字段(如account = ? OR mobile = ? OR email = ?) - 各
OR分支的选择性差异大(一个返回 10 行,另一个返回 800 万行) -
EXPLAIN显示type = ALL或key为空 - 查询带
ORDER BY + LIMIT,但排序字段不在OR的任一分支索引中
改写后每个子查询可独立命中对应字段的索引,UNION ALL 只合并结果,不判重、不排序,开销极低。
UNION ALL 改写的实操要点
直接照着模板改,但注意这些细节:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 每个子查询都要带上完整的过滤条件(比如公共的
AND type = 1 AND closed = 0),不能只写OR部分 - 务必用
UNION ALL,不是UNION;去重由业务逻辑控制,数据库不做 - 如果原查询有
LIMIT,要先在每个子查询里加LIMIT(如LIMIT 10),再在外层统一LIMIT,否则可能漏数据 - 若需排序,把
ORDER BY ... LIMIT放在外层,不要放在每个子查询里(除非你确定每个子查询结果集小且有序) - 字段顺序和类型必须完全一致,否则会报错:
SELECT member_id FROM ...和SELECT id FROM ...不兼容
示例(原语句):
SELECT `member_id` FROM `feel_member` WHERE (`account`='leader' OR `mobile`='leader') AND `type`=1 AND `closed`=0 LIMIT 1;
改写后:
(SELECT `member_id` FROM `feel_member` WHERE `account`='leader' AND `type`=1 AND `closed`=0 LIMIT 1) UNION ALL (SELECT `member_id` FROM `feel_member` WHERE `mobile`='leader' AND `type`=1 AND `closed`=0 LIMIT 1) LIMIT 1;
容易被忽略的索引配合点
光改写还不够,索引得跟上:
- 每个
OR分支字段,单独建单列索引即可(如account、mobile),不用强求复合索引 - 如果分支带范围条件(如
created_at > '2025-01-01'),优先建覆盖索引,减少回表 - 别忘了验证:
EXPLAIN每个子查询,确认type是ref或range,key显示实际索引名 - MySQL 8.0 虽支持
index_merge,但不稳定;线上关键查询仍以UNION ALL+ 明确索引为第一选择
真正卡住性能的,往往不是改写本身,而是改写后没补索引,或者没用 EXPLAIN 确认每个子路径确实走了索引。










