并行聚合在多维查询中未必加速,常因数据倾斜、grouping sets重复扫描或io争抢而变慢;应优先优化数据分布、分区和预聚合,而非盲目开启并行。

并行聚合在多维查询中是否真能加速?先看执行计划
多数人以为开并行就一定快,实际常适得其反。PostgreSQL 10+ 默认启用并行查询,但 max_parallel_workers_per_gather 设为 4 并不等于每次都能跑满——关键看数据分布和分组键基数。
常见错误现象:EXPLAIN (ANALYZE, BUFFERS) 显示多个 Parallel HashAggregate 节点,但 Actual Total Time 比单线程还高;或 Buffers shared read 翻倍,说明 worker 在争抢磁盘 IO。
- 用
SELECT count(*), user_id FROM t GROUP BY user_id ORDER BY count(*) DESC LIMIT 5快速识别头部倾斜键(如 90% 数据集中在 3 个user_id) - 若发现倾斜,别硬上并行:改用
WHERE user_id NOT IN ('hot1','hot2') ... UNION ALL SELECT ... WHERE user_id IN ('hot1','hot2') - MySQL 8.0 的并行仅限
INFORMATION_SCHEMA或 NDB 引擎,InnoDB 表的GROUP BY本质仍是单线程排序聚合
GROUPING SETS + 并行 = 危险组合?必须拆解执行路径
GROUPING SETS 本质是显式枚举分组组合,数据库不会复用中间结果。当你写 GROUP BY GROUPING SETS ((a), (b), (a,b)),引擎大概率执行三次独立扫描——并行只会让这三次扫描同时发生,IO 和 CPU 压力翻三倍,而非计算加速。
实操建议:
- 先用
EXPLAIN确认是否出现多个GroupAggregate或HashAggregate节点;若有,说明已在重复读数据,此时加并行只是放大瓶颈 - 维度 ≤3 且组合明确(如只取
(region),(product),()),可保留GROUPING SETS,但关掉并行(SET LOCAL max_parallel_workers_per_gather = 0) - 维度 ≥4 或含时间桶(如
time_bucket('1h', ts)),直接放弃GROUPING SETS,改用预聚合表按需填充,避免实时计算
真正有效的并行优化:从存储层切分数据
对千万级以上多维表,并行聚合的收益上限很低;更有效的是让数据物理分布匹配查询模式。例如按时间分区 + 按区域哈希分片,使每个 worker 只扫自己负责的子集。
使用场景:
- TimescaleDB:用
create_hypertable按时间+空间(如region_id)双维度切分,GROUP BY time_bucket(), region_id自动下推到各 chunk 并行执行 - ClickHouse:建
ReplacingMergeTree表时指定PARTITION BY toYYYYMM(ts)和ORDER BY (region, product),聚合自动按 partition 并行 - PostgreSQL:手动建分区表(
range分区时间,list分区区域),并在父表上SET parallel_setup_cost = 0强制并行扫描所有分区
GROUPING() 函数在并行环境下容易被忽略的关键细节
并行本身不影响 GROUPING() 语义,但它会放大误用后果:当多个 worker 同时处理不同分组组合,而你没用 GROUPING(col) 区分占位 NULL 和真实 NULL,报表里“总计行”和“真实缺失值”会在合并阶段彻底混淆。
必须做到:
- 所有被折叠的列(即出现在部分
GROUPING SETS元组中但非全部的列),在SELECT中必须配套GROUPING()判断,例如:CASE WHEN GROUPING(dept_name) = 1 THEN '总计' ELSE dept_name END - 禁止写
GROUPING((a,b))—— 语法错误;多列判断要分别调用:GROUPING(a) + GROUPING(b) = 2表示两者均被折叠 - PostgreSQL 支持
GROUPING_ID(a,b,c)返回位掩码,适合复杂组合;MySQL 不支持,跨库迁移时这里必报错
GROUPING SETS 的每组定义都是一次独立扫描,它和并行不是协同关系,而是叠加压力的关系。真正省资源的做法,往往是关掉并行,换索引、换分区、换预聚合。










