having 本身不慢,慢是因为误当 where 使用;它在 group by 后执行,需先完成全量分组聚合再过滤,若将本该前置的行级条件(如时间、状态)放入 having,会导致性能崩盘。

HAVING 本身不慢,慢是因为你把它当 WHERE 用了。 它执行阶段固定在 GROUP BY 之后,所有数据先分组、聚合、生成中间结果集,再逐组判断 HAVING 条件——如果本该提前筛掉 90% 行的条件被塞进 HAVING,数据库就得白干十倍的活。
WHERE 和 HAVING 混用时为什么性能崩盘
典型错误是把行级字段过滤硬塞进 HAVING,比如:
SELECT user_id, COUNT(*) FROM logs GROUP BY user_id HAVING created_at > '2024-01-01';
这会让数据库先对全部历史日志分组(可能上百万组),再丢弃 created_at 不符合的组。而正确路径是让 WHERE 在分组前就把无关数据砍掉:
-
WHERE能走created_at索引,扫描量直降 90%+ -
GROUP BY输入数据量大幅减少,内存占用和 CPU 时间同步下降 - 复合索引设计要覆盖
WHERE条件 +GROUP BY字段,例如(created_at, user_id)
HAVING 中引用别名或未分组字段会报错或结果不可靠
别名在 HAVING 中不可用,不是语法糖,是解析阶段限制。例如:
SELECT user_id AS uid, COUNT(*) AS cnt FROM t GROUP BY user_id HAVING cnt > 10;
PostgreSQL 直接拒绝;MySQL 严格模式下报错;SQL Server 虽允许但语义模糊。必须写原始表达式:
- 用
HAVING COUNT(*) > 10,而不是HAVING cnt > 10 - 若
SELECT里是ROUND(AVG(score), 2),HAVING也得重复写ROUND(AVG(score), 2) > 85 - 非聚合字段如
status出现在HAVING中,必须同时出现在GROUP BY列表里,否则逻辑矛盾
HAVING 条件完全不走索引,优化只能靠前置收缩
HAVING 是内存后处理,索引对它毫无意义。想提速,唯一靠谱动作是压缩 GROUP BY 的输入规模:
- 所有能单行判断的条件,一律挪到
WHERE:时间范围、状态码、类型标识等 - 避免在
WHERE中用函数包裹索引字段,例如WHERE DATE(created_at) = '2024-01-01'会失效,改用WHERE created_at >= '2024-01-01' AND created_at - 如果业务真需要按聚合结果筛选(如
HAVING COUNT(*) > 5),就接受它无法索引的事实,专注把WHERE写 tight
最常被忽略的一点:HAVING 看似“多一层筛选”,实则是最后一步,没有回旋余地。它的性能天花板,完全由 WHERE 和索引决定。











