having 混用导致查询变慢,因其在 group by 和聚合计算后执行,无法利用索引下推,且错误将本该在 where 过滤的非聚合条件(如 status = 'active')放入 having,增大中间结果集;分组字段无索引时更会引发全表扫描和文件排序。

WHERE 和 HAVING 混用时,为什么查询变慢了
因为 HAVING 是在分组后才执行的过滤,它必须等 GROUP BY 完成、聚合函数算完,才能筛数据;而 WHERE 在分组前就过滤掉大量行,能显著减少中间结果集大小。很多慢查询其实是把本该写在 WHERE 里的条件错塞进了 HAVING。
- 比如
HAVING COUNT(*) > 10没问题,但HAVING status = 'active'就是典型错误——status不是聚合字段,也不依赖分组结果,应改用WHERE status = 'active' - 数据库无法对
HAVING条件做索引下推,哪怕对应字段有索引,也得先算完所有分组再过滤 - 某些引擎(如 MySQL 5.7)在
HAVING引用非SELECT列或非分组列时还会报错:Unknown column 'x' in 'having clause'
GROUP BY 字段没索引,HAVING 再快也没用
HAVING 本身不触发索引,但它依赖的分组过程会严重受 GROUP BY 字段是否走索引影响。如果分组字段没索引,数据库大概率要全表扫描 + 文件排序(Using filesort),这时加再多 HAVING 条件都救不回性能。
- 检查执行计划:看到
type: ALL或Extra: Using temporary; Using filesort就说明分组没走索引 - 复合索引要注意顺序:若写
GROUP BY user_id, created_at,索引应建为(user_id, created_at),反过来效果差 - MySQL 8.0+ 支持函数索引,对表达式分组(如
GROUP BY DATE(created_at))可建INDEX (DATE(created_at))
用子查询提前过滤,比硬扛 HAVING 更稳
当业务逻辑强制要求“先分组、再按聚合结果过滤”,又没法优化 GROUP BY 字段索引时,把大表先缩小再分组,往往比直接 GROUP BY ... HAVING 快几倍。
sdxShop是一款完全开源免费的网上独立建店系统,asp+access,程序经过专业团队开发升级发展了7年,功能和安全性已经达到非常成熟稳定,安装容易,一分钟就可以搭起专业的电子商务网站。该免费版功能完整永久免费,主要特色功能淘宝数据表导入,实现网店和淘宝网店数据统一,拓展网店经营策略,提供5种在线支付接口等等。
- 例如统计“近30天下单超5次的用户”:别写
GROUP BY user_id HAVING MAX(order_time) > NOW() - INTERVAL 30 DAY AND COUNT(*) > 5,而是先WHERE order_time > NOW() - INTERVAL 30 DAY,再分组 - 嵌套一层
SELECT * FROM (SELECT ... WHERE ...) t GROUP BY ... HAVING ...,让优化器有机会在子查询阶段用上索引 - 注意 PostgreSQL 对子查询的物化策略(
MATERIALIZEDvsNOT MATERIALIZED),必要时加/*+ MATERIALIZE */提示(Oracle/PG)或强制临时表
MySQL 的 SQL_MODE 会影响 HAVING 行为
MySQL 默认开启 ONLY_FULL_GROUP_BY,这会让看似合法的 HAVING 报错,比如引用了未出现在 GROUP BY 或聚合函数中的非分组列——这不是性能问题,但会阻断上线。
- 错误信息典型长这样:
Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column ... - 关掉它虽能跑通,但语义模糊:数据库可能随机选值,结果不可靠;正确做法是补全
GROUP BY或用ANY_VALUE() - MariaDB 10.3+ 默认更严格,连
ANY_VALUE()都要显式声明,否则直接报错
真正卡住性能的往往不是 HAVING 本身,而是它前面那步分组有没有被索引兜住,以及过滤逻辑有没有被错误地拖到分组之后。调优时盯着 EXPLAIN 里的 rows 和 Extra 字段,比死磕 HAVING 语法重要得多。










