OR导致索引失效的底层原因是B+树为单路径有序结构,无法原生支持多起点并发跳转;当col_a和col_b仅有单列索引时,优化器权衡回表过滤成本后常选择全表扫描。

OR导致索引失效的底层原因
MySQL 的 B+ 树索引是单路径有序结构,无法原生支持“多起点并发跳转”。当 WHERE 中出现 col_a = 1 OR col_b = 2,且 col_a 和 col_b 各自只有单列索引时,优化器面临三种选择:单独走 col_a 索引再过滤 col_b、单独走 col_b 索引再过滤 col_a、或全表扫描。前两者都需回表 + 二次过滤,成本往往高于直接全表扫描,所以优化器常选 type=all。
哪些 OR 实际能走索引?别一概而论
现代 MySQL(5.0+,尤其 8.0)支持 Index Merge,但有严格前提:
-
OR两侧必须都是**独立可命中索引的等值条件**,比如user_id = 1001 OR order_time = '2024-01-01',且两列均有单列索引 - 不能混用函数、范围查询(如
order_time > '2024-01-01')、IS NULL或负向操作(!=) - 执行计划中
key字段会显示为index_merge,type是index_merge,不是all或range - 如果其中一列没索引,或用了
LIKE '%abc',Index Merge直接不触发,退化为全表扫描
EXPLAIN 怎么快速判断 OR 是否翻车
别猜,直接看执行计划:
- 运行
EXPLAIN FORMAT=TRADITIONAL SELECT ... WHERE a=1 OR b=2 - 检查
key是否为NULL或未命中你预期的索引名 - 看
type:若为all,基本确认全表扫描;若为index_merge,说明走了索引合并 - 注意
rows值是否远超实际匹配行数——这说明优化器误判了选择性,可能因统计信息过期,可执行ANALYZE TABLE your_table更新
替代 OR 的实操方案与取舍
不是所有 OR 都要拆,得看场景:
- 同一列多值:优先用
IN,如status IN ('shipped', 'completed')—— 语义清晰,MySQL 对IN有专门优化,比多个OR更稳 - 不同列、且都有索引:先
EXPLAIN,若已是index_merge,无需改;若不是,再考虑UNION ALL - 用
UNION ALL拆分时,务必确保两个子查询返回字段顺序、类型一致,否则报错;UNION会去重,额外排序开销大,无去重要求就别用 - 应用层拆成两次查询再合并,适合结果集小、网络延迟低、且能容忍轻微数据不一致的场景(比如后台导出)
真正容易被忽略的是:OR 的问题不在语法本身,而在它暴露了索引设计缺陷。如果业务频繁需要 col_a = X OR col_b = Y,更治本的解法是建联合索引 (col_a, col_b) 或覆盖索引,而不是在 SQL 层反复绕弯。

















