mysql 中如果条件where中有or,则要求or两边的字段都必须有索引,否则不能用到索引, 为什么?

1. 核心原因:索引的数据结构特性

1.1 B+Tree 索引的扫描方式

MySQL 的 InnoDB 引擎使用 **B+Tree**索引,这种索引的特点是:

  • 有序存储:索引键值按顺序存储

  • 范围查询高效:可以快速定位到范围的起点和终点

  • 最左前缀匹配:必须从索引的最左列开始匹配

1.2 OR 操作的逻辑特性

WHERE condition1 OR condition2意味着:

  • 满足 condition1 ​ condition2 的记录都要返回

  • 相当于两个结果集的并集

2. 具体场景分析

2.1 场景一:所有 OR 字段都有索引

复制代码
-- 假设 name 和 age 字段都有独立索引
SELECT * FROM users WHERE name = 'Alice' OR age = 25;

执行计划

  1. 使用 name索引查找 name = 'Alice'的记录

  2. 使用 age索引查找 age = 25的记录

  3. 对两个结果集进行索引合并(Index Merge)

  4. 合并后去重,回表获取完整数据

结论:可以使用索引,性能较好

2.2 场景二:部分 OR 字段有索引

复制代码
-- 假设 name 有索引,但 age 没有索引
SELECT * FROM users WHERE name = 'Alice' OR age = 25;

执行计划

  1. 使用name索引查找 name = 'Alice'的记录

  2. 对于 age = 25的条件,由于 age 没有索引,必须:

    • 全表扫描​ 所有记录

    • 逐行检查 age = 25的条件

  3. 合并两个结果集

问题

  • 一旦需要全表扫描,使用索引的优势就丧失了

  • 优化器通常会选择直接全表扫描,因为:

    • 索引查找 + 全表扫描的成本 > 直接全表扫描

    • 避免多次随机 I/O 操作

3. 深入理解:为什么不能部分使用索引

3.1 数据访问的物理特性

复制代码
-- 假设有 1000 万条数据
-- name='Alice' 有 100 条(有索引)
-- age=25 有 10 万条(无索引)

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

如果尝试部分使用索引

  1. 通过 name 索引找到 100 条记录(快速)

  2. 需要检查剩下的 999.99 万条记录是否符合 age=25

  3. 检查过程需要:

    • 读取所有数据页

    • 逐行判断 age 条件

  4. 实际上比直接全表扫描更慢

3.2 优化器的决策逻辑

MySQL 优化器会计算各种执行计划的成本:

  • 全表扫描成本:读取所有数据页的成本

  • 索引合并成本:多个索引查找 + 合并 + 回表的成本

  • 部分索引成本:索引查找 + 部分全表扫描的成本

通常结果 :当有字段无索引时,部分索引的成本最高 ,优化器会选择全表扫描。

4. 索引合并(Index Merge)的局限性

4.1 支持的索引合并类型

MySQL 支持三种索引合并:

  • Index Merge Intersection:多个索引的交集(AND)

  • Index Merge Union:多个索引的并集(OR)

  • Index Merge Sort-Union:排序后的并集

4.2 索引合并的前提条件

复制代码
-- 只有 name 和 age 都有索引时,才能使用 Index Merge Union
EXPLAIN SELECT * FROM users 
WHERE name = 'Alice' OR age = 25;

-- 执行计划会显示:
-- type: index_merge
-- Extra: Using union(idx_name, idx_age); Using where

必要条件

  • 每个 OR 条件都必须有可用的索引

  • 索引必须是单列索引或复合索引的最左前缀

  • 查询条件相对简单

5. 实际验证示例

5.1 创建测试表

复制代码
CREATE TABLE user_test (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    age INT,
    email VARCHAR(100),
    INDEX idx_name (name),
    INDEX idx_age (age)
);

-- 插入测试数据
INSERT INTO user_test VALUES 
(1, 'Alice', 25, 'alice@test.com'),
(2, 'Bob', 30, 'bob@test.com'),
(3, 'Charlie', 25, 'charlie@test.com');

5.2 测试不同场景

场景 A:两个字段都有索引

复制代码
EXPLAIN SELECT * FROM user_test 
WHERE name = 'Alice' OR age = 25;

-- 结果:使用 index_merge
-- key: idx_name,idx_age
-- type: index_merge

场景 B:只有 name 有索引

复制代码
-- 删除 age 索引
DROP INDEX idx_age ON user_test;

EXPLAIN SELECT * FROM user_test 
WHERE name = 'Alice' OR age = 25;

-- 结果:全表扫描
-- type: ALL
-- key: NULL

6. 解决方案和最佳实践

6.1 解决方案

方案 1:为所有 OR 字段创建索引

复制代码
-- 确保所有 OR 条件的字段都有索引
CREATE INDEX idx_name ON users(name);
CREATE INDEX idx_age ON users(age);

方案 2:使用 UNION 重写查询

复制代码
-- 原始查询
SELECT * FROM users WHERE name = 'Alice' OR age = 25;

-- 重写为 UNION
SELECT * FROM users WHERE name = 'Alice'
UNION
SELECT * FROM users WHERE age = 25;

方案 3:使用覆盖索引

复制代码
-- 如果只需要索引列,可以避免回表
SELECT name, age FROM users 
WHERE name = 'Alice' OR age = 25;

6.2 最佳实践

  1. 避免在 WHERE 子句中随意使用 OR

  2. 分析查询模式,为频繁查询的字段创建索引

  3. 使用 EXPLAIN 分析执行计划

  4. 考虑使用 UNION 替代 OR

  5. 对于复杂查询,考虑使用全文索引或其他解决方案

7. 总结

MySQL 中 OR 条件要求所有字段都有索引的根本原因是:

  1. B+Tree 索引的特性:只能高效处理索引字段的查询

  2. OR 操作的逻辑:需要获取多个条件的结果集并集

  3. 性能考虑:部分字段无索引时,优化器会选择全表扫描

  4. 索引合并的限制:要求所有参与合并的索引都必须存在

相关推荐
eggwyw2 小时前
完美解决phpstudy安装后mysql无法启动
数据库·mysql
LaughingZhu3 小时前
Product Hunt 每日热榜 | 2026-03-23
数据库·人工智能·经验分享·神经网络·chatgpt
2401_894241923 小时前
用Pygame开发你的第一个小游戏
jvm·数据库·python
java修仙传3 小时前
MySQL 事务隔离级别详解
数据库·mysql·oracle
Irissgwe3 小时前
MySQL存储过程和触发器专题
数据库·mysql·oracle
椎4953 小时前
Redis day02-应用-实战-黑马点评-短信登录
数据库·redis·spring
瀚高PG实验室4 小时前
易智瑞GeoScene Pro连接瀚高安全版数据库 458
数据库·安全·瀚高数据库
551只玄猫4 小时前
【数据库原理 实验报告3】索引的创建以及数据更新
数据库·sql·课程设计·实验报告·操作系统原理