本文目录导读:

这是一个非常经典且重要的数据库索引问题,简单直接的回答是:使用 OR 条件时,索引并不一定会失效,但很容易导致数据库优化器放弃使用索引,转而选择全表扫描。
根本原因在于 索引的结构(通常是B+树)与 OR 逻辑的“多路搜索”需求之间的矛盾。
下面详解其原理、失效场景以及应对策略。
核心原因:单列索引的局限性
假设有一张表 users,有字段 age 和 name,且分别在 age 和 name 上建立了独立的单列索引。
查询语句为:
SELECT * FROM users WHERE age = 25 OR name = 'Tom';
数据库执行过程面临一个选择:
- 走
age索引:找到所有age=25的行 (结果集A)。 - 走
name索引:找到所有name=Tom的行 (结果集B)。 - 合并结果:将结果集A和结果集B进行
UNION(去重合并)。
问题出在第3步:合并结果集,这是一个非常耗时的操作,需要内存排序、临时表等复杂过程,当两个结果集都很大时,这个“合并去重”的开销可能比直接全表扫描还大。
数据库优化器的权衡逻辑:
- 走索引开销:索引A查询成本 + 索引B查询成本 + 合并去重的巨大成本。
- 全表扫描开销:读取整个表,逐行判断
age=25 OR name=Tom。
当 OR 两边的条件都能使用索引,但需要合并两个结果集时,优化器经常认为合并成本 > 全表扫描成本,于是放弃使用索引,选择全表扫描。
导致索引“失效”的几种具体场景
OR 连接了多个字段,且不是复合索引的“最左前缀”
- 场景:
WHERE status = 'active' OR create_date > '2023-01-01' - 索引:
(status, create_date)复合索引。 - 结果: 索引失效,因为
OR要求查询可能走左边(status),也可能走右边(create_date),复合索引是有序排列的,它是一个整体,无法同时从两个不同的入口(status 和 create_date)开始搜索。
OR 连接了索引列和非索引列
- 场景:
WHERE age = 25 (有索引) OR description LIKE '%keyword%' (无索引) - 结果:整个查询很可能走全表扫描,因为
description列没有索引,必须全表扫描,既然总要全表扫一遍,顺便把age=25的行也找出来,对优化器来说更高效。
即使是同一字段的 OR
- 场景:
WHERE age = 25 OR age = 30 - 结果:索引可能生效,因为
age = 25 OR age = 30等价于age IN (25, 30),优化器通常能将其优化为一次范围扫描(在B+树中取两个叶子节点即可),这是少数OR能走索引的场景。
MySQL 老版本优化器限制
- 在 MySQL 5.0 及更早版本中,优化器处理
OR的能力很弱,几乎一定会放弃使用单列索引,MySQL 5.6 以后引入了 Index Merge 优化,但正如第一点所说,它依然需要评估合并成本。
如何解决 OR 导致的索引失效问题?
有几种成熟的优化方案:
改写为 UNION ALL / UNION(最推荐)
将 OR 拆分成两个独立的查询,然后用 UNION 合并,这强制了数据库分别走索引,然后合并结果。
-- 原来可能的慢查询 SELECT * FROM users WHERE age = 25 OR name = 'Tom'; -- 改写为效率更高的形式 SELECT * FROM users WHERE age = 25 UNION ALL -- 如果明确没有重复行,用 UNION ALL 避免去重,速度更快 SELECT * FROM users WHERE name = 'Tom';
为什么更快? 因为每个子查询都完美地使用了单列索引,最后步骤(UNION)是明确的。
使用复合索引覆盖所有 OR 条件(最彻底)
创建一个包含所有 OR 条件中字段的复合索引,这样,这两个条件就在同一个索引结构里,可以直接进行范围扫描。
- 场景:
WHERE age = 25 OR name = 'Tom' - 索引:
CREATE INDEX idx_age_name ON users(age, name); - 关键:MySQL 的引擎(如 InnoDB)可以对复合索引进行多个条件的搜索,避免合并,但需要注意索引顺序(最左前缀原则)。
使用 IN 代替 OR(仅限同一字段)
OR 连接的是同一个字段的多个值,直接换成 IN。
-- 不好 WHERE age = 25 OR age = 30; -- 好 WHERE age IN (25, 30);
IN 通常会被优化器很好地处理为多个等值查询或一个范围查询。
总结与记忆点
| 场景 | 是否走索引 | 原因 |
|---|---|---|
同一字段 OR (age=25 OR age=30) |
可能走 | 优化器可转为 IN 或范围扫描。 |
| 不同字段 OR,且各自有单列索引 | 可能不走 | 合并结果集成本 > 全表扫描成本。 |
| 不同字段 OR,其中一列无索引 | 一定不走 | 有索引的那一列也白建,因为必须全表扫。 |
| OR 条件在复合索引中非最左列 | 一定不走 | 复合索引的有序特性无法支持多入口搜索。 |
一句话口诀:
“OR 如分叉路,索引怕两顾,单列各自走,合并成本高,要么用 UNION,要么建联合。”
最佳实践建议:
- 优先改写成
UNION ALL(最安全、通用性强)。 - 如果查询很频繁,考虑建复合索引(最优解,但增加写负担)。
- 避免在
OR中混合索引列和非索引列(这是最坏的情况)。