2. 聚合查询性能模型:理解聚合算子(HashAgg、SortAgg)的执行模型与代价模型
聚合查询是数据库中最常见的性能瓶颈场景之一。在尾延迟压测中,聚合算子的选择直接决定了查询的响应时间分布。本章深入剖析两种核心聚合算子——HashAgg 与 SortAgg 的执行模型与代价模型,为后续调优提供理论基础。
2.1 聚合算子执行模型对比
HashAgg 与 SortAgg 在内存使用、计算路径和适用场景上存在本质差异。下表从关键维度进行对比:
| 对比维度 | HashAgg | SortAgg |
|---|---|---|
| 核心数据结构 | 哈希表(Hash Table) | 排序缓冲区 + 顺序扫描 |
| 分组方式 | 哈希计算 + 冲突解决 | 排序后相邻行合并 |
| 内存需求 | 与分组基数成正比 | 与输入数据量成正比 |
| 是否依赖有序输入 | 否 | 是(需排序或索引有序) |
| 适用分组基数 | 低~中等(内存可容纳) | 高基数(数据量大) |
| 溢出处理 | 磁盘溢出(性能急剧下降) | 外部归并排序(可控降级) |
| 尾延迟稳定性 | 较差(哈希冲突 + 溢出抖动) | 较好(排序行为可预测) |
压测启示: 在尾延迟压测场景中,HashAgg 的哈希冲突和磁盘溢出会导致明显的长尾效应;而 SortAgg 由于排序过程的可预测性,尾延迟分布更集中。选择聚合算子时,需结合分组基数和内存限制综合评估。
2.2 HashAgg 执行模型与代价分析
HashAgg 的执行分为三个阶段:构建哈希表 → 聚合计算 → 结果输出。其代价模型主要由以下因素决定:
- 哈希计算代价: 每个输入行计算分组键的哈希值,复杂度 O(1) 但存在 CPU 开销。
- 冲突解决代价: 哈希冲突导致链表遍历或开放寻址,冲突率随负载因子上升。
- 内存分配代价: 哈希表初始化和扩容操作,涉及内存分配与数据重哈希。
- 磁盘溢出代价: 当内存不足时,部分哈希表写入磁盘,后续读取产生 I/O 抖动。
HashAgg 的总代价可近似表示为:
Cost_HashAgg = N * (C_hash + C_conflict) + M * C_mem + O_disk * C_io
其中:N 为输入行数,C_hash 为单行哈希计算代价,C_conflict 为平均冲突处理代价,M 为哈希表大小,O_disk 为溢出数据量。
尾延迟陷阱: 当分组基数超过内存阈值时,HashAgg 的溢出行为会导致延迟从微秒级跳变到毫秒级,形成明显的尾延迟拐点。压测中应监控
work_mem 与哈希表大小的比例。
2.3 SortAgg 执行模型与代价分析
SortAgg 的执行分为两个阶段:排序 → 顺序聚合。其代价模型更依赖于排序操作的效率:
- 排序代价: 基于输入数据量 N 和排序算法复杂度 O(N log N)。
- 内存排序代价: 当数据量小于
work_mem时,完全在内存中完成快速排序。 - 外部排序代价: 数据量超过内存时,触发归并排序,产生多次磁盘读写。
- 顺序聚合代价: 排序完成后,仅需一次线性扫描即可完成分组聚合,代价 O(N)。
SortAgg 的总代价可近似表示为:
Cost_SortAgg = N * log(N) * C_sort + N * C_agg + O_merge * C_io
其中:C_sort 为单次比较交换代价,C_agg 为单行聚合计算代价,O_merge 为外部归并的 I/O 次数。
关键洞察: SortAgg 的排序代价虽然在前端较高,但排序后的聚合过程完全线性且无冲突,尾延迟分布更平滑。在压测中,SortAgg 的 P99 延迟通常比 HashAgg 更稳定,尤其在高分组基数场景下。
2.4 聚合算子选择决策流程
下图展示了数据库优化器在选择聚合算子时的典型决策路径:
2.5 代价模型关键参数与压测指标
在尾延迟压测中,以下参数直接影响聚合算子的性能表现:
| 参数 | 影响算子 | 压测关注点 | 典型调优方向 |
|---|---|---|---|
work_mem |
HashAgg / SortAgg | 内存溢出阈值 | 增大内存或改用磁盘友好算子 |
| 分组基数(NDV) | HashAgg | 哈希冲突率 | 优化哈希函数或切换 SortAgg |
| 输入数据量 | SortAgg | 排序时间 | 利用索引预排序或增加排序内存 |
| 数据倾斜度 | HashAgg | 热点桶冲突 | 使用两阶段聚合或倾斜优化 |
| 并行度 | 两者 | 数据分片与合并开销 | 调整并行 worker 数量 |
压测实践建议: 在尾延迟压测中,建议同时测试 HashAgg 和 SortAgg 两种算子在同一数据集上的 P50、P99、P99.9 延迟。若 HashAgg 的 P99.9 延迟超过 P50 的 10 倍以上,说明哈希冲突或溢出已严重影响尾延迟,应考虑强制使用 SortAgg 或优化分组键设计。
2.6 执行模型对尾延迟的直接影响
从执行模型角度,两种算子在尾延迟上的差异可归纳为:
- HashAgg 的尾延迟特征: 正常情况延迟较低,但遇到哈希冲突链过长或磁盘溢出时,延迟出现突发尖峰,形成“长尾”。压测中表现为 P99 突然跳变。
- SortAgg 的尾延迟特征: 排序阶段延迟相对均匀,外部归并排序的 I/O 行为可预测,尾延迟分布更集中,P99 与 P50 的比值通常较小。
- 混合场景: 部分数据库支持 Hybrid Agg,先使用 HashAgg 在内存中聚合部分数据,溢出后转为 SortAgg,但切换过程本身可能引入额外延迟抖动。
调优口诀: 低基数用 Hash,高基数用 Sort;内存充足用 Hash,内存紧张用 Sort;尾延迟敏感用 Sort,吞吐优先用 Hash。