最有效的手段是直接用 grouping sets 或子查询预聚合以避免多次全表扫描;grouping sets 一次性完成多维聚合,mysql 不支持,postgresql 等支持;标量子查询应改为聚合子查询后 join;cte 可复用中间聚合结果;务必优先下推 where 过滤条件。

直接用 GROUPING SETS 或子查询预聚合,能避免多次全表扫描——这是最有效的手段。
用 GROUPING SETS 一次性完成多维聚合
当你要按不同粒度(比如国家、国家+省份、国家+省份+城市)分别求 SUM(sales),传统写法是三个独立 GROUP BY 查询,触发三次表扫描。而 GROUPING SETS 让数据库只扫一次表,内部按多个分组集并行计算:
- 必须确保所有分组字段都出现在 SELECT 列表中,未参与当前分组的列会返回
NULL,需配合GROUPING()函数识别层级 - MySQL 不支持
GROUPING SETS(截至 2026 年仍无原生实现),PostgreSQL、SQL Server、Oracle 和 DuckDB 均可用 - 示例写法:
SELECT country, province, city, SUM(sales) AS total_sales FROM sales_data GROUP BY GROUPING SETS ( (country), (country, province), (country, province, city) );
先聚合再 JOIN:避免标量子查询反复扫描
标量子查询(如 (SELECT SUM(amount) FROM order_items WHERE order_id = o.id))在主表每行执行一次,等于对 order_items 扫描 N 次。换成聚合子查询后,只需一次扫描 + 索引关联:
- 子查询必须只包含分组键和聚合值,例如
SELECT order_id, SUM(amount) AS total FROM order_items GROUP BY order_id - 外层
JOIN的 ON 条件要能命中order_id上的索引(最好是主键或唯一索引) - 务必把时间过滤等高选择性条件下推到子查询里,比如
WHERE created_at >= '2026-08-01',否则聚合范围过大反而更慢
用 CTE 提取公共聚合结果复用
如果多个聚合逻辑共享同一中间结果(比如都要基于「近 7 天用户行为汇总」算 UV、PV、平均停留时长),CTE 能保证底层数据只扫描计算一次:
- CTE 不是视图,不物化;但现代优化器(如 PostgreSQL 12+、SQL Server)通常会将 WITH 子句内联并重用执行计划
- 避免在 CTE 中写
SELECT *,只选真正需要的列,减少内存和网络传输开销 - 若 CTE 结果集较大且被多次引用,某些引擎(如 Spark SQL)可能自动缓存,但 PostgreSQL 默认不会,此时应考虑临时表
最容易被忽略的是:聚合前的 WHERE 过滤位置。哪怕只是把 HAVING dept = 'IT' 改成 WHERE dept = 'IT',就能让扫描行数从百万级降到几千——这比任何语法技巧都管用。扫描次数降不下来,其他优化都是在给 IO 做无用功。











