SQL优化--OR
优化 SQL 中的 OR
条件是一个非常常见的性能调优问题。OR
操作符经常会导致性能下降,因为它使得数据库优化器难以高效地使用索引。
下面我将从浅入深地为你讲解优化 OR
的多种策略,并附上示例。
为什么 OR
性能往往较差?
在简单的 WHERE
子句中,每个条件通常可以利用自己的索引。但是,当使用 OR
连接两个针对不同列的条件时,数据库可能无法同时使用两个索引(在旧版本或某些情况下),从而导致全表扫描。
优化策略一览
以下是优化 OR
条件的几种核心方法,你可以根据实际情况选择:
使用
UNION
/UNION ALL
拆分查询 (最有效、最常用的方法)使用
IN
操作符 (当OR
作用于同一字段时)使用多列索引 (针对特定的
OR
条件组合)使用
CASE
语句和函数索引 (较为复杂,特定场景下使用)重构逻辑:使用
AND
和OR
的组合
方法一:使用 UNION
或 UNION ALL
(首选方案)
这是优化不同列上 OR
条件的最强大和最可靠的方法。它的思路是将一个包含 OR
的查询拆分成多个独立的、都能高效使用索引的查询,然后将结果合并。
优化前:
SELECT *
FROM orders
WHERE status = 'shipped' OR customer_id = 123;
假设 status
和 customer_id
上分别有索引,但这个查询可能只能使用其中一个索引,或者直接进行全表扫描。
优化后:
SELECT * FROM orders WHERE status = 'shipped'
UNION ALL
SELECT * FROM orders WHERE customer_id = 123;
UNION ALL
: 比UNION
更快,因为它不去重。如果你的两个查询条件绝无可能重叠,或者你允许重复数据,一定要用UNION ALL
。UNION
: 会去除重复的行,开销更大。只有在可能出现重复记录且你需要去重时才使用它。
为什么更优?
现在每个 SELECT
语句都可以充分利用自己的索引:
第一个查询使用
idx_status
索引。第二个查询使用
idx_customer_id
索引。
数据库最终将两个高效的独立查询结果合并起来,通常比扫描整张表要快得多。
方法二:使用 IN
操作符 (针对同一字段)
如果 OR
是用在同一个字段上,将其改为 IN
操作符是更简洁的做法。数据库优化器对 IN
的处理通常非常高效。
优化前:
SELECT *
FROM products
WHERE category_id = 1 OR category_id = 2 OR category_id = 7;
优化后:
SELECT *
FROM products
WHERE category_id IN (1, 2, 7);
这两句在逻辑上完全等价,但 IN
的写法更简洁,并且数据库优化器可以非常完美地利用 category_id
上的索引。
注意:IN
只能用于优化同一字段的 OR
条件。对于不同字段的 OR
,IN
无能为力。
方法三:使用多列索引 (覆盖索引)
如果你的 OR
条件查询是某个高频且固定的模式,可以考虑创建一个覆盖这些列的索引。
例如,对于查询 WHERE a = 1 OR b = 2
,可以创建一个索引 on (a, b)
。
但是要注意:多列索引对于 WHERE a = 1 AND b = 2
非常有效,但对于 OR
,效果可能不如 UNION
。有些数据库(如 MySQL)在某些情况下可以对这种查询使用索引合并(Index Merge),即同时使用 idx_a
和 idx_b
两个索引,然后合并结果,但这并不总是发生,而且效率通常不如 UNION
方案稳定。
所以,通常建议将 UNION
作为首选,创建多列索引更多是为了优化 AND
条件。
方法四:重构逻辑(巧用德摩根定律)
有时候,你可以通过应用逻辑定律来重写查询,避免使用 OR
。
根据德摩根定律:NOT (A AND B) = (NOT A) OR (NOT B)
我们可以反过来运用它。例如,一个复杂的查询可能被重写:
优化前:
SELECT *
FROM users
WHERE (status = 'active' AND last_login > '2023-01-01')OR (status = 'inactive' AND signup_source = 'google');
这个查询可能不太好优化。
有时可以尝试重写为:
SELECT *
FROM users
WHERE status = 'active' AND last_login > '2023-01-01'
UNION ALL
SELECT *
FROM users
WHERE status = 'inactive' AND signup_source = 'google';
(看,又回到了我们万能的方法一)
总结与建议
场景 | 推荐优化方法 | 示例 |
---|---|---|
OR 作用于不同字段 | UNION ALL / UNION | WHERE a=1 OR b=2 -> 拆成两个 SELECT 用 UNION ALL 连接 |
OR 作用于同一字段 | IN | WHERE id=1 OR id=2 OR id=3 -> WHERE id IN (1,2,3) |
固定模式的高频查询 | 考虑创建多列索引 | 对 (a, b) 建索引,但需测试效果是否优于 UNION |
复杂条件混合 | 重构逻辑 + UNION | 将复杂条件拆解,分别优化,最后合并 |
最终建议:
首先检查执行计划: 在任何优化前后,都使用
EXPLAIN
(MySQL/PostgreSQL)或EXPLAIN PLAN
(Oracle)来查看数据库的实际执行路径,这是性能调优的金科玉律。首选
UNION ALL
: 对于不同字段的OR
条件,UNION ALL
在绝大多数情况下都是最佳选择。记得确认是否可以用UNION ALL
代替UNION
。索引是基础: 确保
OR
条件中涉及的每个字段都有合适的索引。没有索引,任何优化技巧都效果有限。保持简洁: 对于同一字段的多个
OR
,果断改用IN
。
通过结合以上策略和分析执行计划,可以有效地解决大多数由 OR
引起的 SQL 性能问题。