WHERE在分组前过滤行,可利用索引高效减少IO;HAVING在分组后过滤结果,不能用索引,仅支持聚合函数或GROUP BY字段,二者执行顺序为WHERE→GROUP BY→HAVING。

WHERE条件能走索引,HAVING基本不走
WHERE子句的过滤发生在数据扫描阶段,只要字段上有合适索引(比如age列建了B+树索引),MySQL就能用索引快速定位或跳过大量数据页。而HAVING是分组聚合完成之后才执行的,此时数据已在内存中组织成结果集,它无法利用原始表的索引——哪怕你写HAVING id IN (1,2,3),也得先算完所有分组再过滤。
常见错误是把本该放WHERE的条件挪到HAVING里,比如:
SELECT department, COUNT(*) FROM employees GROUP BY department HAVING department = 'tech'; -- ❌ 错误:department不是聚合结果,且没走索引
正确做法是提前用WHERE过滤:
SELECT department, COUNT(*) FROM employees WHERE department = 'tech' -- ✅ 索引可用,减少GROUP BY输入量 GROUP BY department;
聚合函数只能出现在HAVING里,不能放WHERE中
当你需要基于统计值筛选时,WHERE直接报错,因为聚合函数在WHERE执行时尚未计算。例如下面这句会触发Invalid use of group function:
SELECT user_id, SUM(amount) FROM orders WHERE SUM(amount) > 1000 -- ❌ 报错:SUM在WHERE阶段不可用 GROUP BY user_id;
必须改用HAVING:
SELECT user_id, SUM(amount) AS total FROM orders GROUP BY user_id HAVING total > 1000; -- ✅ 正确:聚合已完成,total是合法别名
- 别名在
HAVING中可以直接用(如total),但不能在WHERE中用 -
HAVING里允许写SUM(amount) > 1000,也允许写COUNT(*) >= 5,但不能出现未分组的普通字段(如status = 'paid') - 如果
status也在GROUP BY里,那它就成了“聚合键”,才能在HAVING中参与判断
没有GROUP BY时,HAVING也能用,但要小心语义
标准SQL允许HAVING单独存在,此时整张表被视作一个分组。这种写法少见但有用,比如检查表是否为空或数据连续性:
SELECT 'table has data' AS status FROM users HAVING COUNT(*) > 0;
但要注意:HAVING里不能引用任何非聚合、非常量字段。下面这句会报Unknown column 'name' in 'having clause':
SELECT * FROM users HAVING name = 'Alice'; -- ❌ 错误:name未聚合,也不在GROUP BY中
这种场景应该用WHERE。只有当你要对全表聚合结果做判断时,HAVING才有意义。
组合使用WHERE + HAVING才是高性能写法
真实业务查询往往需要两层过滤:先用WHERE缩小原始数据范围,再用HAVING筛聚合结果。比如查“2024年入职、且部门平均薪资超15K的团队”:
SELECT department, AVG(salary) AS avg_salary FROM employees WHERE hire_date >= '2024-01-01' -- ✅ 先过滤掉旧员工,减少分组数据量 GROUP BY department HAVING avg_salary > 15000; -- ✅ 再筛高薪部门
性能差异可能极大:一张千万级员工表,WHERE能干掉95%的数据,GROUP BY就只处理几十万行;若全靠HAVING,就得先聚合全部千万行再过滤,内存和CPU压力完全不同。
容易被忽略的是WHERE条件的位置——它必须写在GROUP BY之前,且不能依赖任何聚合别名;而HAVING里的字段要么是聚合函数,要么是GROUP BY中明确列出的列。

















