where在分组前过滤原始行,having在分组后过滤聚合结果;where可利用索引、减少计算量,having无法走索引、强制全量分组,性能开销更大。

WHERE在分组前过滤,HAVING在分组后过滤
数据库执行SQL时有固定顺序:FROM → WHERE → GROUP BY → HAVING。这意味着WHERE作用于原始行,能直接丢弃大量不匹配的记录;而HAVING必须等GROUP BY完成所有分组和聚合计算(如COUNT()、SUM())之后,才开始筛选分组——此时数据已膨胀为中间聚合结果,计算量和内存占用都更大。
HAVING强制全量分组,无法跳过无关行
哪怕你只想保留销售额超10万的客户,HAVING SUM(amount) > 100000仍要求数据库先对**所有客户**执行分组和求和。而如果条件可下推到WHERE(比如WHERE order_date >= '2025-01-01'),数据库就能跳过历史订单,大幅减少参与分组的行数。
- 常见反模式:
SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id HAVING COUNT(*) > 100—— 若95%客户订单数≤100,这部分计算全白做 - 优化写法:若业务允许加前置约束(如只查近半年),应补上
WHERE order_date > DATE_SUB(NOW(), INTERVAL 6 MONTH)
HAVING依赖的列/表达式无法走索引
WHERE中使用的字段(如user_id、status)通常有索引支持,过滤极快;但HAVING里的条件基于聚合结果(如AVG(rating)、COUNT(*)),这些值不存在于原表,数据库无法利用索引,只能扫描全部分组结果逐个比对。
- 注意:
HAVING里用非聚合字段(如HAVING department = 'Sales')是无效的——该字段必须出现在GROUP BY中,否则报错或行为未定义 - 某些MySQL版本允许
HAVING引用SELECT别名(如HAVING avg_salary > 5000),但这只是语法糖,底层仍要算完再筛
组合使用WHERE+HAVING才是常态,但别让HAVING干WHERE的活
真正影响性能的不是HAVING本身,而是把它当成“兜底过滤器”滥用。例如:
- 错误:用
HAVING过滤时间范围(HAVING created_at > '2025-01-01')——created_at是原表字段,必须且只能放WHERE - 正确:用
WHERE筛时间 +HAVING筛聚合结果,如WHERE status = 'paid' GROUP BY product_id HAVING SUM(quantity) > 1000
最易被忽略的一点:当GROUP BY字段基数极高(如按order_id分组),HAVING几乎失去意义——每个分组只有1行,聚合函数结果恒为1,此时HAVING COUNT(*) > 1永远不成立,但数据库仍会傻算一遍。










