别让or嵌套在子查询里,先拆再连,否则基本等于放弃索引;因mysql优化器难以处理子查询内的or,易导致全表扫描、临时表和文件排序,应改用union all或拆分exists并配独立索引。

直接结论:别让OR嵌套在子查询里,先拆再连,否则基本等于放弃索引。
为什么OR嵌套子查询会变慢
MySQL对OR的处理本身就不友好,一旦它出现在子查询内部(比如WHERE ... IN (SELECT ... WHERE a=1 OR b=2)),优化器几乎无法生成有效执行计划。更糟的是,外层查询若再套一层逻辑,EXPLAIN里大概率出现type: ALL或Using temporary; Using filesort——说明已经在全表扫描+内存排序了。
常见错误现象包括:查询耗时从毫秒级跳到秒级、CPU打满、SHOW PROCESSLIST里长期卡在Sending data状态。
- OR条件跨字段(如
status = 1 OR created_at > '2025-01-01')时,复合索引很难覆盖全部路径 - 子查询里用
OR+GROUP BY或ORDER BY,会强制物化临时表 - 嵌套层级超过2层,优化器可能直接放弃索引合并(
index_merge)策略
把OR子查询改写成UNION ALL + JOIN
核心思路是:把“一个含OR的子查询”变成“多个独立走索引的子查询”,再用JOIN或IN对接主表。比强行保留嵌套结构可靠得多。
例如原语句:
SELECT * FROM orders WHERE user_id IN ( SELECT id FROM users WHERE name = 'Alice' OR email LIKE '%@gmail.com' );
应拆解为:
SELECT DISTINCT o.* FROM orders o INNER JOIN ( SELECT id FROM users WHERE name = 'Alice' UNION ALL SELECT id FROM users WHERE email LIKE '%@gmail.com' ) u ON o.user_id = u.id;
- 每个
SELECT分支都单独走索引(需确保name和email有独立索引) - 用
UNION ALL而非UNION,避免去重开销;外层DISTINCT只在必要时加 - 若
users表很大,email LIKE '%@gmail.com'仍可能走全表扫描——这时要评估是否加函数索引,或改用全文索引
用EXISTS替代IN + OR子查询
当子查询返回大量ID、或存在NULL值风险时,IN语义容易出错,EXISTS更安全且通常更快。
原写法:
SELECT * FROM products WHERE id IN (SELECT product_id FROM sales WHERE region = 'CN' OR status = 'shipped');
优化后:
SELECT * FROM products p
WHERE EXISTS (
SELECT 1 FROM sales s
WHERE s.product_id = p.id
AND (s.region = 'CN' OR s.status = 'shipped')
);
但注意:这个写法依然保留了OR,所以必须配合索引——sales(product_id, region)和sales(product_id, status)两个单列索引,才能触发index_merge。否则还是得拆成两个EXISTS:
SELECT * FROM products p WHERE EXISTS ( SELECT 1 FROM sales s WHERE s.product_id = p.id AND s.region = 'CN' ) OR EXISTS ( SELECT 1 FROM sales s WHERE s.product_id = p.id AND s.status = 'shipped' );
容易被忽略的索引细节
很多人加了索引就以为万事大吉,但OR对索引要求极苛刻:
-
OR两边的字段必须都有独立索引,不能只靠一个复合索引——INDEX(a,b)对a=1 OR b=2完全无效 - 字符串比较时注意隐式类型转换:
user_id = 123 OR user_id = '123'会导致索引失效,统一用INT或VARCHAR -
OR中混用函数(如UPPER(name) = 'ALICE' OR email = 'a@b.com')会让整个条件退化,优先提取可索引部分 - 用
EXPLAIN FORMAT=JSON查index_key和using_index_merge字段,比看key列更准
真正麻烦的从来不是写SQL,而是验证它到底有没有走你期待的那条路。每次改完,务必跑一遍EXPLAIN,盯着rows和Extra字段看——哪怕只是多扫了1000行,上线后也可能放大成雪崩。











