or查询变慢本质是索引失效,因优化器遇任一不可索引分支即弃整体索引;应优先用in替代同字段多值or,或用union all拆分跨字段or并确保各子查询独立走索引。
or 条件让查询变慢,本质是索引失效
mysql 对 or 的处理很保守:只要其中任一条件无法使用索引(比如字段无索引、用了函数、类型隐式转换),整个 where 子句就可能退化为全表扫描。即使所有字段都有单列索引,优化器也常放弃使用它们,尤其当 or 分支超过 3 个时。
把 OR 拆成 UNION ALL 是最直接的解法
用 UNION ALL 替代多个 OR 条件,能让每个分支独立走索引。前提是各分支结果集互斥(或允许重复),且字段完全一致。
- ✅ 正确写法:
(SELECT id, name FROM users WHERE status = 'active') UNION ALL (SELECT id, name FROM users WHERE type = 'vip') - ❌ 错误写法:
SELECT * FROM users WHERE status = 'active' OR type = 'vip'(易触发全表扫描) - ⚠️ 注意:如果两个条件可能返回同一行,
UNION ALL会保留重复;需要去重则用UNION,但性能略低
改写为 IN 或 EXISTS 前要验证执行计划
IN 看似简洁,但仅当右侧是**少量确定值**时才安全;若值来自子查询或数量过大(如 IN (1,2,...,500)),MySQL 可能转为临时表或全量扫描。而 EXISTS 在关联子查询场景下更可控,但需确保子查询里有索引字段。
- ✅ 推荐:
SELECT * FROM orders o WHERE EXISTS (SELECT 1 FROM users u WHERE u.id = o.user_id AND u.level IN ('gold', 'platinum')) - ⚠️ 避免:
SELECT * FROM logs WHERE level IN (SELECT level FROM config_levels)(子查询无索引时风险高) - ? 务必用
EXPLAIN确认是否走了索引,别凭直觉
真正治本:检查字段索引与数据分布
很多 OR 性能问题,根源不在写法,而在字段本身不适合走索引——比如 status 只有 3 个值,选择性极低,建索引意义不大;而 user_id 或 created_at 就适合。
- 查选择性:
SELECT COUNT(DISTINCT status)/COUNT(*) FROM users;结果 - 优先建复合索引:
ALTER TABLE users ADD INDEX idx_status_type (status, type);,再把OR改成IN或拆分查询 - Navicat 里右键表 → “对象信息” → “索引”,快速核对现有索引是否覆盖你的查询字段
最常被忽略的一点:OR 查询在 Navicat 里看起来只是语法调整,但实际执行路径可能从毫秒级跳到秒级。别只盯着 SQL 写法,先用 EXPLAIN 看清数据库到底怎么执行的——那才是性能真相的起点。











