← 返回题目列表

MySQL 中 OR、IN 和 UNION 查询如何优化?

高频 中等 第 11 / 28 题 更新于 2026/07/29
MySQLORINUNIONSQL 优化

简化版

ORINUNION 都可能影响索引选择和扫描范围;优化时要看条件是否能分别走索引、结果是否需要去重、列表是否过大,必要时把复杂 OR 拆成 UNION ALL 或临时表 JOIN。

详细版

简单 IN 通常能被优化成多个等值查找,例如 id IN (1,2,3)。但 IN 列表过大、子查询返回太多、类型不一致时,性能会变差。

OR 的问题在于不同分支可能涉及不同列和索引。如果每个分支都能高效走索引,优化器可能使用 index merge;如果某个分支选择性很差,整体可能退化成扫描大量数据。

UNION 默认去重,会有额外排序或临时表成本;如果业务允许重复或分支互斥,应优先考虑 UNION ALL。面试中要强调:改写前后结果语义必须一致,不能为了性能改变去重逻辑。

完整版教学

一、这类题考的是“多条件怎么走索引”

ORINUNION 都会让优化器面对多个候选范围。

SELECT *
FROM orders
WHERE user_id = 100 OR status = 'paid';

这里可能走 user_id 索引,也可能走 status 索引,也可能合并两个索引结果,还可能扫描表。决定因素是选择性、统计信息、返回列和成本估算。

记忆钩子:多条件优化不是背关键字,而是看每个分支能不能缩小数据范围。

二、IN 适合少量等值匹配

IN 常用于少量枚举或主键集合。

SELECT *
FROM users
WHERE id IN (10, 20, 30);

如果 id 是主键,数据库可以做多个点查找,效率很好。

但如果列表很大,例如一次传 5 万个 id,SQL 文本变长、优化成本上升、网络和解析压力也会增加。可以把 id 放进临时表,再 JOIN:

SELECT u.*
FROM users u
JOIN tmp_ids t ON t.id = u.id;

大列表优化要看数据库版本、参数和数据量,但“无限拼 IN 列表”通常不是好习惯。

三、OR 可能走 index merge,也可能变慢

如果 OR 两边都有索引,MySQL 可能使用 index merge,把多个索引结果合并。

CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_status ON orders(status);

SELECT *
FROM orders
WHERE user_id = 100 OR status = 'paid';

但如果 status='paid' 命中 80% 的表,走索引再回表可能不划算,优化器可能选择全表扫描。

条件选择性可能表现
user_id=100点查很快
status='paid'命中大量行
两者 OR混合成本不一定好

所以看到 OR 慢,先看每个分支的过滤能力。

四、什么时候拆成 UNION ALL

如果 OR 分支分别适合不同索引,可以考虑拆成多个查询。

SELECT *
FROM orders
WHERE user_id = 100
UNION ALL
SELECT *
FROM orders
WHERE status = 'paid';

这样每个分支有机会独立选择更合适索引。但要注意:如果同一行同时满足两个条件,UNION ALL 会返回重复行,而原始 OR 只返回一次。

如果需要去重,可以用 UNION,但它有去重成本;也可以确保分支互斥:

SELECT *
FROM orders
WHERE user_id = 100
UNION ALL
SELECT *
FROM orders
WHERE status = 'paid' AND user_id <> 100;

改写必须先保证语义一致。

五、UNION 和 UNION ALL 成本不同

UNION 默认去重,相当于合并后还要判断重复。

SELECT id FROM a
UNION
SELECT id FROM b;

UNION ALL 不去重,直接拼接结果:

SELECT id FROM a
UNION ALL
SELECT id FROM b;

如果两个分支天然互斥,比如一个查 status='paid',另一个查 status='cancelled',就没必要用 UNION 去重。

UNION:合并 -> 去重 -> 输出
UNION ALL:合并 -> 输出

数据量越大,去重成本越明显。

六、类型一致和函数包裹也会影响优化

多条件查询常见隐形问题是类型不一致。

-- user_id 是 BIGINT,却传字符串列表
WHERE user_id IN ('1', '2', '3')

MySQL 可能做隐式转换,严重时影响索引使用或结果判断。还有函数包裹列:

WHERE DATE(created_at) = '2026-07-29'

这会让索引列不能直接按范围定位。应改成:

WHERE created_at >= '2026-07-29'
  AND created_at <  '2026-07-30'

优化 OR/IN/UNION 时,要顺手检查类型、函数、排序和返回列。

七、常见误区与追问

  • 误区:IN 一定比 OR 快。 简单等值条件可能相近,真正差异取决于索引、列表规模和优化器。
  • 误区:所有 OR 都必须改成 UNION 只有当分支能独立更好走索引且语义可保持时才值得改。
  • 误区:UNIONUNION ALL 只是写法不同。 UNION 去重,可能带来临时表和排序成本。
  • 误区:大 IN 列表无限拼接没问题。 列表过大可能增加解析、优化和执行成本。
  • 追问:UNION ALL 会改变结果吗? 如果分支有重叠,会产生重复行;需要去重或构造互斥条件。
  • 追问:OR 分支不同索引怎么办?EXPLAIN 是否 index merge,必要时拆分查询验证成本。

八、加强记忆

OR/IN/UNION 优化先看语义,再看索引。小 IN 点查没问题,大列表考虑临时表;OR 慢先分析每个分支选择性;能互斥就用 UNION ALL,需要去重才用 UNION