mysql中or导致索引失效,主因是优化器难以合并多字段索引路径,b+树不支持并集扫描;若任一条件无索引或存在隐式转换、表达式等,即触发全表扫描;推荐用union all拆分并确保各分支独立走索引。

不是“必须两边都有索引才能生效”,而是:只要有一边没索引,MySQL 优化器大概率放弃索引走全表扫描——因为部分用索引反而更慢。
OR 条件下索引失效的物理原因
B+Tree 索引是有序结构,天然适合「单路径扫描」。而 WHERE a = 1 OR b = 2 是两个独立条件的并集,MySQL 需要分别定位满足任一条件的行。
如果只有 a 有索引、b 没索引:
- 它能用索引快速找到
a = 1的几十行 - 但为了检查剩下的全部数据是否满足
b = 2,必须逐行读取所有数据页(即全表扫描) - 此时「索引查找 + 全表扫描」的 I/O 成本 > 单纯一次全表扫描
- 优化器权衡后,直接选
type = ALL
Index Merge 是唯一能“两边都走索引”的机制
只有当 a 和 b 各自拥有**单列索引**(如 INDEX(a) 和 INDEX(b)),且查询中没有函数、表达式或隐式转换时,MySQL 才可能启用 Index Merge。
验证是否生效,只看 EXPLAIN 输出:
- ✅ 成功:
type = index_merge,key字段显示多个索引名,Extra出现Using union(...) - ❌ 失败:
type = ALL或key = NULL,哪怕字段上有索引也白搭 - ⚠️ 注意:联合索引
INDEX(a, b)对a = 1 OR b = 2基本无效——b = 2不符合最左前缀,B+Tree 根本不按b排序
为什么不用 UNION ALL 替代 OR 更靠谱?
UNION ALL 是显式拆分逻辑,绕过优化器“猜成本”的不确定性:
- 每个子查询都能稳定命中对应索引:
SELECT * FROM t WHERE a = 1走INDEX(a),SELECT * FROM t WHERE b = 2走INDEX(b) - 避免了
Index Merge在低区分度字段(如status只有 'active'/'inactive')上被优化器主动弃用的问题 - 去重需手动控制:第二句加
AND a != 1(确保语义等价),但注意字段类型、NULL 处理必须完全一致,否则UNION会报错
真正容易被忽略的点
很多人建了索引就以为万事大吉,但实际生效还卡在几个隐蔽环节:
-
OR两边字段类型不一致(比如一边是VARCHAR一边是INT)→ 触发隐式转换 → 索引失效 - 用了表达式:
WHERE a + 1 = 2 OR b = 3→a + 1让INDEX(a)彻底作废 - 数据量小(比如几千行)或选择度极差(如性别字段)→ 优化器直接判“没必要用索引”
- 没跑
EXPLAIN确认,仅凭“我建了索引”做判断











