rollup 比普通 group by 慢因强制多层嵌套分组、重复扫描、无法用松散索引扫描,必触发 using temporary 和 using filesort,中间结果爆炸式增长且串行执行。

GROUP BY ROLLUP 为什么比普通 GROUP BY 慢得多
因为 ROLLUP 不是“多算几组”那么简单,它强制 MySQL/PostgreSQL 执行多层嵌套分组 + 重复扫描,且无法利用松散索引扫描(Using index for group-by),几乎必然触发 Using temporary 和 Using filesort。
普通 GROUP BY a, b 最多生成 N×M 个分组;而 GROUP BY a, b WITH ROLLUP 要生成 (N+1) × (M+1) 个分组,并额外补全 NULL 行——这些行不是简单叠加,数据库得反复重排、合并、再排序。尤其当任意字段基数高(比如 user_id 或 order_no)时,中间结果集体积爆炸式增长,内存撑不住就直接落磁盘临时表。
- ROLLUP 的每一级汇总都依赖上一级结果,无法并行或流式处理,执行路径是串行的
- 即使
a和b都有索引,ROLLUP 也无法用索引有序扫描,因为 NULL 行必须插在特定位置,物理顺序被破坏 - MySQL 8.0+ 的
hash_group_by对 ROLLUP 无效,退回到基于排序的老逻辑,I/O 压力翻倍
EXPLAIN 里看到哪些信号说明 ROLLUP 正在拖垮性能
跑 EXPLAIN FORMAT=TRADITIONAL SELECT ... GROUP BY ... WITH ROLLUP 后,紧盯 Extra 列:
- 出现
Using temporary; Using filesort:确认已掉进磁盘临时表陷阱,不是“慢一点”,而是 I/O 瓶颈 -
key_len明显小于预期(比如索引定义是(region, city),但key_len只显示 region 部分长度):说明只有第一级分组走了索引,ROLLUP 后续层级完全没用上 -
rows预估远高于实际 WHERE 过滤后行数:优化器误判了 ROLLUP 中间态规模,导致分配资源严重不足
注意:type 是 index 或 range 并不表示 ROLLUP 快——它只说明主扫描阶段走了索引,ROLLUP 的聚合阶段仍要重建临时结构。
替代 ROLLUP 的三种实操方案
别硬扛 ROLLUP,业务上真正需要的往往只是某几层汇总,而不是全量立方体。
- 用
UNION ALL显式拼接各级汇总:例如先查GROUP BY region, city,再查GROUP BY region,最后查总计,每条子查询都能走独立索引,还能加WHERE提前过滤 - 预聚合到汇总表:对高频报表维度(如日粒度+区域+品类),用定时任务写入
summary_daily_region_category表,查询时直接SELECT * FROM ... WHERE dt = '2026-08-26' - 应用层组装:查出明细(如
SELECT region, city, amount FROM sales WHERE ...),在 Python/Java 里用pandas.groupby(...).agg(...)或Stream.collect(Collectors.groupingBy())计算各级汇总——把 CPU 和内存压力从 DB 移到应用,DB 只负责 IO
ROLLUP 的语法糖代价很高,它省掉的是几行 SQL,换来的是不可控的执行计划和随时可能崩掉的响应时间。真正难的不是写出 ROLLUP,而是判断哪一层汇总真的被业务用了——多数时候,90% 的 ROLLUP 结果行根本没人看。











