grouping sets 不一定比 rollup 快,性能取决于执行计划而非语法;postgresql 和 sql server 通常生成相同计划,oracle 12c+ 在统计信息缺失时可能更慢;mysql 8.0 不支持 grouping sets,rollup 为伪实现。

GROUPING SETS 比 ROLLUP 快吗?看执行计划,不是看写法
快不快,取决于数据库实际生成的执行计划,而不是你写了 GROUPING SETS 还是 ROLLUP。PostgreSQL 和 SQL Server 通常会对等价的 ROLLUP(a,b) 和 GROUPING SETS((a,b),(a),()) 生成完全一样的计划;但 Oracle 12c+ 在某些统计信息缺失时,可能对 GROUPING SETS 多做一次哈希重分区,反而慢 10%~20%。
实操建议:
- 先用
EXPLAIN (ANALYZE, BUFFERS)(PostgreSQL)或SET STATISTICS XML ON(SQL Server)比对真实执行路径,别信语法“看起来更直接” - 如果字段顺序敏感(比如
a高基数、b低基数),显式写GROUPING SETS((a,b),(a),())可能比ROLLUP(a,b)更容易触发索引跳扫(尤其在 PostgreSQL 中配合INDEX ON t(a,b)) -
CUBE(a,b)等价于GROUPING SETS((a,b),(a),(b),()),但多数引擎会把CUBE当作独立算子优化——Oracle 会尝试位图转换,而 DuckDB 目前仍拆成多个UNION ALL,性能差距可达 3×
GROUPING SETS 的 NULL 值陷阱:不是数据为空,是分组标记
很多人看到结果里某列出现 NULL,就以为原始数据丢了,其实那是 GROUPING() 函数的标记机制在起作用——NULL 表示该维度被“折叠”,不是原始值为 NULL。
常见错误现象:
- 用
WHERE col IS NULL过滤汇总行,误删真实数据中的NULL记录 - 没加
GROUPING()判断,直接在应用层把所有NULL当“总计行”处理,导致明细和汇总混在一起 - 在
ORDER BY里只排字段,不按GROUPING(col)分层,导致小计行插在明细中间
正确做法是显式识别:例如 SELECT a, b, SUM(x), GROUPING(a) AS ga, GROUPING(b) AS gb FROM t GROUP BY GROUPING SETS((a,b),(a),()),再用 ga=1 AND gb=1 定位全汇总行。
ROLLUP/CUBE 在 MySQL 8.0+ 是伪实现,GROUPING SETS 不支持
MySQL 8.0 虽然加了 ROLLUP 语法,但它底层是改写成多个 UNION ALL + 单层 GROUP BY,没有真正的多维聚合算子。这意味着:
- 每组
GROUP BY都要全表扫描一遍,4 维ROLLUP就是 2⁴ = 16 次扫描(实际优化后略少,但仍是 O(2ⁿ)) -
CUBE在 MySQL 中根本不可用,会报错ERROR 1235 (42000): This version of MySQL doesn't yet support 'CUBE' - 想用
GROUPING SETS?不行。MySQL 至今(8.0.33)仍不支持该语法,必须手写UNION ALL或换引擎(如 ClickHouse、DuckDB)
如果你在 MySQL 里硬要模拟 GROUPING SETS((a),(b),()),最稳的写法是:(SELECT a, NULL AS b, SUM(x) FROM t GROUP BY a) UNION ALL (SELECT NULL, b, SUM(x) FROM t GROUP BY b) UNION ALL (SELECT NULL, NULL, SUM(x) FROM t),注意加括号和字段对齐。
大数据量下,预聚合比任何 GROUPING 方式都管用
当单表超千万行、维度超 3 个时,无论 GROUPING SETS 还是 ROLLUP,都会面临内存溢出或临时磁盘爆满。这时候语法差异已经不重要了,关键是你有没有提前建好物化聚合表。
实操建议:
- 用定时任务(如 pg_cron)每天凌晨跑:
INSERT INTO agg_daily_a_b SELECT a, b, COUNT(*), SUM(revenue) FROM fact_sales WHERE dt = CURRENT_DATE - INTERVAL '1 day' GROUP BY a, b - 对高频查询的组合(如
(region, product_category)),单独建索引:CREATE INDEX idx_agg_region_cat ON agg_daily_a_b(region, product_category) - 避免在大宽表上直接跑
GROUPING SETS——即使引擎支持,也大概率触发HashAgg: Rehashing due to memory limit(PostgreSQL)或Warning: Hash aggregate spilled to disk(SQL Server)
真正卡住性能的,从来不是语法选错,而是忘了数据规模已经超出单次聚合的合理边界。










