having不走索引是因为它在group by之后执行,操作对象是无索引的内存中间结果;正确做法是将可下推条件移至where,确保索引在扫描阶段生效。

MySQL 的 HAVING 为什么完全不走索引
HAVING 不走索引,不是 MySQL 懒得优化,而是执行阶段根本没索引可用——它运行在 GROUP BY 完成之后,此时原始行已消失,只留下内存中的分组聚合结果。索引是建在表的物理存储结构(B+ 树叶子节点)上的,而 HAVING 操作的对象是临时生成的、无持久存储的中间结果集,数据库连“往哪找索引”都不知道。
HAVING 条件里写 status = 'paid' 就会触发全表扫描
这类写法看似合理,实则违反 SQL 执行顺序和语义约束:
-
status是单行字段,未出现在GROUP BY子句中,也未被聚合,HAVING无法确定“这个分组的 status 到底是什么值” - MySQL 5.7+ 开启
ONLY_FULL_GROUP_BY后直接报错:Expression #2 of SELECT list is not in GROUP BY clause - 即使旧版 MySQL 或某些配置下“侥幸通过”,执行计划里也会出现
type: ALL和Extra: Using temporary; Using filesort,说明全表扫 + 内存排序已发生 - 正确做法永远是:把
status = 'paid'移到WHERE,让索引在扫描阶段就生效
HAVING COUNT(*) > 100 看似合理,但性能隐患藏在分组基数里
当 GROUP BY 字段区分度极高(比如按 order_id 分组),每个分组只有一行,COUNT(*) 恒为 1,HAVING COUNT(*) > 100 永远不成立——但 MySQL 仍会老老实实算完全部分组再丢弃结果。
- 分组字段没索引?
GROUP BY本身就会强制全表扫描 + 文件排序,HAVING 只是雪上加霜 - 检查执行计划:若
key为空、rows接近表总行数、Extra含Using temporary,说明分组阶段已失控 - 复合索引要覆盖
WHERE条件 +GROUP BY字段,例如WHERE created_at > ? GROUP BY user_id,索引应建为(created_at, user_id)
别名、函数表达式在 HAVING 中不能省略重写
HAVING 解析发生在 SELECT 列别名生成之前,所以以下写法在 MySQL、PostgreSQL、SQL Server 中均不可靠:
- 错误:
SELECT user_id, COUNT(*) AS cnt FROM t GROUP BY user_id HAVING cnt > 10→ 大部分引擎报Unknown column 'cnt' in 'having clause' - 必须写成:
HAVING COUNT(*) > 10,哪怕COUNT(*)在SELECT里已出现多次 - 如果
SELECT用了ROUND(AVG(score), 2),HAVING也得一模一样写HAVING ROUND(AVG(score), 2) > 85,少一个括号或参数都可能失效











