mysql优化器对or条件保守处理,因索引合并成本高而倾向全表扫描;in通过排序+二分查找更高效;union all拆分查询最可控;复合索引适用于同一维度组合筛选,关键在理解业务语义。

OR条件让优化器“不敢用索引”,不是bug是成本权衡
MySQL优化器对OR的处理非常保守:只要任一侧条件无法走索引(比如字段没索引、类型不匹配、用了函数),整个查询就大概率放弃所有索引,直接type: ALL。这不是它不会,而是它算过——合并多个索引片段要回表、去重、排序,随机IO开销可能比顺序扫全表还高。
典型表现:EXPLAIN里key为NULL,哪怕name和age各自有单列索引,WHERE name = 'a' OR age = 25仍走全表扫描。
- MySQL 8.0+ 更倾向启用
index_merge,但依然不稳定;小表、低选择度字段(如gender)、复合索引与单列索引共存时,常被跳过 - 数据分布影响极大:如果
age = 25命中90%行,优化器会认为走索引反而更慢 -
OR中混用等值和范围(如status = 'paid' OR created_at > '2025-01-01')会让index_merge几乎失效
IN比OR高效得多,本质是二分查找替代线性匹配
IN不是语法糖,它是被深度优化过的:优化器会对IN列表去重、升序排序,再用二分查找匹配索引项,时间复杂度从OR的O(n)降到O(log n)。
比如WHERE user_id IN (3,1,4,1,5)会被处理成有序数组[1,3,4,5],而等价的user_id = 3 OR user_id = 1 OR ...只能逐个判断。
- 单字段多值场景,优先改写为
IN,别硬扛OR -
IN里塞10000个值没问题,但要注意max_allowed_packet和网络传输开销 - 若值来自子查询,
IN可能退化为DEPENDENT SUBQUERY,此时不如改用JOIN
UNION ALL不是万能解药,但最可控
把WHERE a = 1 OR b = 2拆成两个独立查询,强制每条都走自己的索引,这是目前上线风险最低、效果最稳的方案。
但必须避开几个坑:
- 字段顺序、类型、是否允许
NULL必须完全一致,否则报ERROR 1222;别用SELECT *,显式写出列名 - 原查询带
LIMIT或ORDER BY?不能只在外层加,得分别在子查询里加,否则可能漏数据或错序 - 如果业务能确认
a = 1和b = 2天然互斥,第二条里的AND a != 1可删——UNION ALL不查重,性能更高 - 结果集有重复且必须去重?用
UNION,但会触发Using temporary; Using filesort,反而拖慢
什么时候该建复合索引而不是拆OR?
不是所有OR都适合拆。如果逻辑本质是同一维度的组合筛选,建复合索引更干净。
例如:WHERE (status = 'paid' AND created_at > '2025-01-01') OR (status = 'refunded' AND created_at > '2025-01-01') → 直接建(status, created_at)索引,type: range就能覆盖。
- 慎用场景:字段值高度重叠(如
name和email都可能含'admin'),UNION去重逻辑会引入临时表和文件排序 - 复合索引要按业务高频顺序排:等值条件放前,范围条件放后;
WHERE a = ? AND b > ?,索引应为(a, b),不是(b, a) - 已有单列索引的情况下,新增复合索引前先确认是否冗余——
(a, b)已存在,再建a单列索引就没必要
UNION还是复合索引,而是判断OR背后有没有隐藏的业务语义约束。比如status IN ('paid', 'refunded')和status = 'paid' OR amount > 1000,前者建索引,后者拆查询——差的不是技术,是读懂where背后的业务意图。











