having条件下推到子查询的本质是提前过滤,即在分组前通过将having条件下推至子查询的where阶段,减少分组键数量、内存占用及无效聚合;mysql 8.0.22+需显式启用derived_condition_pushdown且子查询须为不可合并派生表,否则下推失效。

HAVING 条件下推到子查询的本质是提前过滤
因为 HAVING 在逻辑执行顺序中排在 GROUP BY 之后,它作用于已分组的中间结果集;而子查询(尤其是派生表)若能提前把 HAVING 中的条件“下推”到其内部的 WHERE 或更早阶段,就能在分组前就筛掉大量无关行——减少分组键数量、降低内存占用、避免无效聚合计算。
MySQL 8.0.22+ 支持 HAVING → WHERE 下推需显式启用
社区版默认不自动将 HAVING 谓词下推至子查询,必须满足两个前提:
-
optimizer_switch中的derived_condition_pushdown设置为on - 子查询必须是不可合并的派生表(例如含
GROUP BY、DISTINCT、UNION或聚合函数)
否则即使 SQL 语义上等价,优化器也不会重写。可手动加 /*+ DERIVED_CONDITION_PUSHDOWN(dt) */ 提示强制启用,其中 dt 是派生表别名。
常见失效场景:哪些 HAVING 条件无法下推?
以下情况会导致下推失败或被忽略:
-
HAVING中引用了外部查询的列(如HAVING dt.avg_price > o.min_threshold) - 条件含非确定性函数(
NOW()、RAND()) - 子查询用了
SQL_NO_CACHE或FOR UPDATE等禁止重写的提示 -
HAVING表达式依赖多个聚合结果(如HAVING MAX(x) - MIN(x) > 100),优化器可能因代价估算放弃下推
此时执行计划中仍可见 Using temporary; Using filesort,且 EXPLAIN 的 Extra 列不会出现 Using where with pushed condition。
手写改写比依赖自动下推更可控
当自动下推不可靠时,直接重构 SQL 往往更高效:
- 把原
HAVING条件拆出来,作为子查询的WHERE(适用于单层聚合) - 用
JOIN替代含HAVING的子查询(例如先算好满足条件的分组 ID,再关联主表) - 对高频使用的聚合结果建物化视图或汇总表,绕过实时计算
自动下推是优化器的“尽力而为”,但聚合逻辑越复杂、数据倾斜越严重,它越容易退回到保守策略——真正稳住性能的,还是你对数据分布和业务约束的明确表达。










