MySQL优化器对OR采用保守估算,因成本模型判定索引路径更慢而弃用;index_merge极难触发,应改用IN或UNION ALL,并确保各子查询索引有效、逻辑一致、条件完整。

MySQL优化器对OR的保守估算逻辑
它不是“不想用索引”,而是算出来用索引更慢。当WHERE中出现OR,优化器会评估每个分支的访问成本:如果任一分支无法走索引(比如name = 'a'没索引),它就放弃整个条件的索引路径,直接选type: ALL——因为“一边走索引 + 一边全表扫 + 合并去重”的预估开销,往往高于一次顺序扫描。
这个决策基于成本模型,不是bug,也不是配置能调出来的。哪怕id = 1有主键索引,只要旁边跟着OR email LIKE '%@qq.com',EXPLAIN里key就一定是NULL。
index_merge不是稳定解,别依赖它
即使name和age各自有单列索引,WHERE name = 'x' OR age = 25也极少触发index_merge。它只在极窄条件下生效:数据量小、选择度高、MySQL 8.0+、无复合索引干扰、且两边都是等值查询。
- 混用等值与范围(如
status = 'paid' OR created_at > '2025-01-01')会让index_merge基本失效 - 有复合索引存在时,
index_merge更可能被压制——优化器认为“走一个复合索引 + 过滤”比“合并两个单列索引”更可控 -
EXPLAIN里没出现type: index_merge,就等于它没启用,别猜
IN和UNION ALL是真正可控的替代路径
单字段多值OR(如status = 'A' OR status = 'B')必须改写为status IN ('A', 'B');跨字段或混合条件(如name = 'a' OR email = 'b@x.com')必须拆成UNION ALL。
但拆之前得验证三件事:
- 每个子查询单独跑
EXPLAIN SELECT * FROM t WHERE ...,确认type是ref或range,且key命中对应索引 - 业务上确认分支是否天然不重叠;若可能重复,
UNION ALL会返回重复行,得看应用能否接受 - 原查询若有
LIMIT或ORDER BY,必须下推到每个子查询里,不能只在外层加,否则分页错乱、排序失效
复合索引有时比拆OR更干净
如果OR本质是同一维度的组合(如(status = 'paid' AND created_at > '2025-01-01') OR (status = 'refunded' AND created_at > '2025-01-01')),建(status, created_at)复合索引,type: range就能覆盖,比UNION ALL少一次合并、少一次网络往返。
但要注意:
- 字段值高度重叠(如
name和email都含'admin')时,UNION ALL可能触发Using temporary,反而比原OR还慢 -
IS NULL分支无法靠普通索引走,得建函数索引(如INDEX (col, id))才能进索引扫描 - 所有公共过滤条件(如
deleted = 0)必须复制进每个子查询的WHERE,漏一个就逻辑错误
最常被忽略的是:UNION ALL各子查询的SELECT列表必须列数、顺序、类型、NULL性完全一致,否则直接报ERROR 1222。这不是语法糖,是执行计划层面的硬约束。

















