带大量or条件的group by查询几乎必然触发全表扫描和using temporary,单纯加索引无效;必须重写为union all或提前物化过滤结果。

直接结论:带大量 OR 条件的 GROUP BY 查询几乎必然触发全表扫描和 Using temporary,单纯加索引无效;必须重写为 UNION ALL 或提前物化过滤结果。
为什么 OR 会让 GROUP BY 索引失效
MySQL 优化器对 OR 的处理非常保守:哪怕每个 OR 分支字段都有单独索引,只要不是同一条索引能覆盖全部条件(比如 WHERE a = 1 OR b = 2),就大概率放弃走索引,退回到 type: ALL 扫描。更糟的是,GROUP BY 需要先拿到所有满足 OR 的行,再分组——这意味着临时表、排序、磁盘落盘三连击。
常见错误现象:EXPLAIN 显示 type: ALL + Extra: Using temporary; Using filesort,哪怕表只有几十万行,查询也可能卡在秒级甚至分钟级。
- 单列索引对
OR基本没用,联合索引也无法同时匹配多个OR分支 -
IN替代OR仅在等值条件下有效(如WHERE status IN ('a','b','c')),但若混用不同字段(WHERE id = 1 OR name = 'x'),IN也救不了 - 函数包裹(如
WHERE UPPER(name) = 'X' OR id > 100)会进一步加剧索引失效
用 UNION ALL 替代 OR 是最稳解法
把逻辑拆成多个独立子查询,各自走自己的索引,再合并结果。MySQL 能为每个 SELECT 单独选择最优执行路径,避免“被一个 OR 拖垮全局”。
示例(原查询):
SELECT region, COUNT(*) FROM orders WHERE user_id = 123 OR order_status = 'shipped' OR created_at > '2026-05-01' GROUP BY region;
重写为:
SELECT region, COUNT(*) FROM orders WHERE user_id = 123 GROUP BY region UNION ALL SELECT region, COUNT(*) FROM orders WHERE order_status = 'shipped' AND user_id != 123 GROUP BY region UNION ALL SELECT region, COUNT(*) FROM orders WHERE created_at > '2026-05-01' AND user_id != 123 AND order_status != 'shipped' GROUP BY region;
- 每个分支必须有对应索引:
(user_id)、(order_status)、(created_at)或更优的联合索引(如(created_at, region)) - 后两个分支加
AND排除已覆盖的行,避免重复计数(业务允许去重时可用UNION,但性能略差) - 如果分支太多(>5),考虑是否真需要实时查——这类查询往往更适合预聚合或异步统计
临时表 + 索引比硬扛 OR 更快
当 OR 条件复杂、字段类型不一(如混合字符串匹配、范围查询、存在性判断),UNION ALL 维护成本高,可先用临时表收拢结果,再分组。
步骤:
- 用多个快速查询分别插入临时表:
INSERT INTO tmp_ids SELECT id FROM orders WHERE user_id = 123;、INSERT INTO tmp_ids SELECT id FROM orders WHERE order_status = 'shipped';等 - 对
tmp_ids建唯一索引避免重复:CREATE UNIQUE INDEX idx_id ON tmp_ids(id); - 最后关联主表分组:
SELECT o.region, COUNT(*) FROM orders o JOIN tmp_ids t ON o.id = t.id GROUP BY o.region;
优势:每个 INSERT 都能走各自索引,临时表体积可控;劣势:多一次写入开销,需注意 tmp_table_size 是否够存中间 ID 列表(建议用 CREATE TEMPORARY TABLE 而非内存表)。
最容易被忽略的陷阱:OR 和 GROUP BY 字段交叉污染
如果 OR 条件里包含 GROUP BY 字段本身(例如 WHERE region = 'CN' OR region = 'US'),看起来像能走索引,但实际仍可能失效——因为优化器无法保证该字段在所有分支中都参与过滤,尤其当 OR 中还混有其他非索引字段时。
此时真正有效的做法是:
- 删掉这个看似“合理”的
OR,改用IN:WHERE region IN ('CN', 'US'),并确保region有索引 - 如果
OR不可避免(比如动态拼接条件),优先在应用层拆分请求,而不是塞进一条 SQL - 检查
SQL_MODE是否含only_full_group_by,它会让某些重写后的查询报错,需配合ANY_VALUE()或调整GROUP BY列表
真正难优化的从来不是 GROUP BY 本身,而是它前面那堆失控的 OR —— 它们让数据库失去了做任何聪明事的基础。











