为什么使用OR条件时索引会失效?

wen IT资讯 241

本文目录导读:

为什么使用OR条件时索引会失效?

  1. 核心原因:单列索引的局限性
  2. 导致索引“失效”的几种具体场景
  3. 如何解决 OR 导致的索引失效问题?
  4. 总结与记忆点

这是一个非常经典且重要的数据库索引问题,简单直接的回答是:使用 OR 条件时,索引并不一定会失效,但很容易导致数据库优化器放弃使用索引,转而选择全表扫描。

根本原因在于 索引的结构(通常是B+树)OR 逻辑的“多路搜索”需求之间的矛盾

下面详解其原理、失效场景以及应对策略。

核心原因:单列索引的局限性

假设有一张表 users,有字段 agename,且分别在 agename 上建立了独立的单列索引

查询语句为:

SELECT * FROM users WHERE age = 25 OR name = 'Tom';

数据库执行过程面临一个选择:

  1. age 索引:找到所有 age=25 的行 (结果集A)。
  2. name 索引:找到所有 name=Tom 的行 (结果集B)。
  3. 合并结果:将结果集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 中混合索引列和非索引列(这是最坏的情况)。

抱歉,评论功能暂时关闭!