跳到主要内容
版本:Latest-4.0

查询优化方案

一个实用的操作手册:症状 → 根本原因 → 经过验证的解决方案
当你打开一个 profile 并发现一个警示指标,但仍需回答“接下来怎么办?”时使用。


1 · 快速诊断流程

  1. 浏览执行概览
    如果 QueryPeakMemoryUsagePerNode > 80 %QuerySpillBytes > 1 GB,直接跳到内存和溢出方案。

  2. 找到最慢的 Pipeline / Operator
    ⟶ 在 Query Profile UI 中点击 Sort by OperatorTotalTime %
    最热的 operator 告诉你接下来要阅读哪个方案块(Scan, Join, Aggregate, …)。

  3. 确认瓶颈子类型
    每个方案以其_特征_指标模式开始。在尝试解决方案之前匹配这些模式。


2 · 按 Operator 分类的方案

2.1 OLAP / Connector Scan [metrics]

为了更好地理解 Scan Operator 中的各种指标,以下图示展示了这些指标与存储结构之间的关联。

profile_scan_relationship

为了从磁盘检索数据并应用谓词,存储引擎使用了几种技术:

  1. 数据存储:编码和压缩的数据以段的形式存储在磁盘上,并附带各种索引。
  2. 索引过滤:引擎利用 BitmapIndex、BloomfilterIndex、ZonemapIndex、ShortKeyIndex 和 NGramIndex 等索引来跳过不必要的数据。
  3. 下推谓词:简单的谓词,如 a > 1,被下推到特定列进行评估。
  4. 延迟物化:仅从磁盘中检索所需的列和过滤后的行。
  5. 非下推谓词:无法下推的谓词会被评估。
  6. 投影表达式:计算诸如 SELECT a + 1 的表达式。

Scan Operator 使用一个额外的线程池来执行 IO 任务。因此,该节点的时间指标关系如下图所示:

profile_scan_time_relationship

常见性能瓶颈

冷或慢存储 – 当 BytesReadScanTimeIOTaskExecTime 占主导且磁盘 I/O 徘徊在 80–100% 时,扫描命中了冷数据或规格不足的存储。将热点数据迁移到 NVMe/SSD,并启用 Data Cache。通过 BE datacache_*(或兼容的旧版 block_cache_*)参数进行容量配置,并通过会话变量 enable_scan_datacache 在扫描时使用缓存。

缺少谓词下推 – 若 PushdownPredicates 接近 0 且 ExprFilterRows 较高,说明谓词未下推到存储层。重写为简单比较(避免 %LIKE% 与过宽的 OR 链),或添加 Bloom 索引、物化视图以支持下推。

线程池饥饿 – 当 IOTaskWaitTime 高且 PeakIOTasks 低时,说明 I/O 并发已饱和。提高 BE 的 I/O 任务并行度并不能缓解。建议启用并合理配置 Data Cache(BE datacache_* 与会话 enable_scan_datacache),将热点数据迁移到更快的存储(NVMe/SSD),并降低上游并发(例如降低 pipeline_dop 或避免过多并发扫描)。

Tablet 数据倾斜 – 最大与最小 OperatorTotalTime 差距很大,说明部分 Tablet 负载远高于其它。可按更高基数键重分桶,或增加桶数以均衡负载。

Rowset/Segment 碎片化RowsetsReadCount/SegmentsReadCount 激增且 SegmentInitTime 较长,表明存在大量小 Rowset。执行手动 Compaction,并将小批量导入合并为批量写入,以便段提前合并。

软删除累积DeleteFilterRows 很大,说明软删除使用较多。运行 BE 的 Compaction 以清理软删除。

2.2 聚合 [metrics]

aggregation_operator Aggregate Operator 负责执行聚合函数、GROUP BYDISTINCT

多种形式的聚合算法

形式规划器选择的条件内部数据结构亮点 / 注意事项
哈希聚合键适合内存;基数不极端使用 SIMD 探测的紧凑哈希表默认路径,非常适合适中键数
排序聚合输入已经按 GROUP BY 键排序简单的行比较 + 运行状态零哈希表成本,通常在探测重偏斜时快 2-3 倍
可溢出聚合 (3.2+)哈希表超出内存限制混合哈希/合并与磁盘溢出分区防止 OOM,保持管道并行度

多阶段分布式聚合

在 StarRocks 中,聚合以分布式方式实现,具体取决于查询模式和优化器的决策,可以是多阶段的。

┌─────────┐        ┌──────────┐        ┌────────────┐        ┌────────────┐
│ Stage 0 │ local │ Stage 1 │ shard/ │ Stage 2 │ gather/│ Stage 3 │ final
│ Partial │───► │ Update │ hash │ Merge │ shard │ Finalize │ output
└─────────┘ └──────────┘ └────────────┘ └────────────┘
阶段使用条件发生了什么
单阶段DISTRIBUTED BYGROUP BY 的子集,分区是共置的部分聚合立即成为最终结果。
两阶段(本地 + 全局)典型的分布式 GROUP BYStage 0 在每个 BE 内部自适应地折叠重复项;Stage 1 基于 GROUP BY 洗牌数据然后执行全局聚合
三阶段(本地 + 洗牌 + 最终)DISTINCT 和高基数 GROUP BYStage 0 如上;Stage 1 按 GROUP BY 洗牌,然后按 GROUP BYDISTINCT 聚合;Stage 2 合并部分状态为 GROUP BY
四阶段(本地 + 部分 + 中间 + 最终)DISTINCT 和低基数 GROUP BY引入额外阶段按 GROUP BYDISTINCT 洗牌以避免单点瓶颈

常见性能瓶颈

高基数 GROUP BY – 当 HashTableSizeHashTableMemoryUsage 膨胀到内存限制时,分组键太宽或太独特。启用排序流聚合(enable_streaming_preaggregation = true),创建汇总物化视图,或将宽字符串键转换为 INT

洗牌倾斜 – 各片段之间 HashTableSizeInputRowCount 的巨大差异揭示了不平衡的洗牌。向键添加盐列或使用 DISTINCT [skew] 提示以便行均匀分布。

状态重的聚合函数 – 如果 AggregateFunctions 主导运行时间且函数包括 HLL_BITMAP_COUNT(DISTINCT),则巨大的状态对象正在被移动。在导入期间预计算 HLL/bitmap 草图或切换到近似变体。

部分聚合降级 – 巨大的 InputRowCount 和适中的 AggComputeTime,加上上游 EXCHANGE 中巨大的 BytesSent,意味着预聚合被绕过。通过 SET streaming_preaggregation_mode = "force_preaggregation" 强制重新启用。

昂贵的键表达式 – 当 ExprComputeTimeAggComputeTime 相当时,GROUP BY 键是逐行计算的。在子查询中物化这些表达式或将它们提升为生成列。

2.3 Join [metrics]

join_operator

Join Operator 负责实现显式连接或隐式连接。

在执行过程中,join operator 被分为构建(哈希表构建)和探测阶段,这些阶段在管道引擎中并行运行。向量块(最多 4096 行)使用 SIMD 批量哈希;消耗的键生成运行时过滤器——Bloom 或 IN 过滤器——被推回上游扫描以提前减少探测输入。

Join 策略

StarRocks 依赖于一个向量化的、管道友好的哈希连接核心,可以连接到四种物理策略中,成本优化器在计划时会权衡这些策略:

策略优化器选择的条件使其快速的原因
Colocate Join两个表属于同一个共置组(相同的桶键、桶数和副本布局)。无网络洗牌:每个 BE 仅连接其本地桶。
Bucket-Shuffle Join其中一个连接表具有与连接键相同的桶键只需洗牌一个连接表,可以减少网络成本
Broadcast Join构建端非常小(行/字节阈值或显式提示)。小表被复制到每个探测节点;避免洗牌大表。
Shuffle (Hash) Join一般情况,键不对齐。在连接键上对每行进行哈希分区,以便探测在 BEs 之间均衡分布。

常见性能瓶颈

构建端过大BuildHashTableTimeHashTableMemoryUsage 的峰值显示构建端已超出内存。交换探测/构建表,预过滤构建表,或启用哈希溢出。

缓存不友好的探测 – 当 SearchHashTableTime 占主导地位时,探测端不是缓存高效的。对探测行按连接键排序并启用运行时过滤器。

洗牌倾斜 – 如果单个片段的 ProbeRows 远超其他片段,数据是倾斜的。切换到更高基数的键或附加盐,如 key || mod(id, 16)

意外广播 – 连接类型 BROADCAST 伴随巨大的 BytesSent 意味着你认为小的表并不小。降低 broadcast_row_limit 或使用 SHUFFLE 提示强制洗牌。

缺少运行时过滤器 – 微小的 JoinRuntimeFilterEvaluate 以及全表扫描表明运行时过滤器从未传播。将连接重写为纯等式并确保列类型对齐。

非等式回退 – 当 operator 类型是 CROSSNESTLOOP 时,不等式或函数阻止了哈希连接。添加一个真正的等式谓词或预过滤较大的表。

2.4 Exchange (网络) [metrics]

过大的洗牌或广播 – 如果 NetworkTime 超过 30% 且 BytesSent 很大,说明传输了过多数据。请重新评估连接策略并减少 Shuffle/广播体量(例如强制使用 Shuffle 替代 Broadcast,或在上游预过滤)。

接收器积压 – 接收器无法跟上时,接收器的高 WaitTime 以及发送者队列始终满。增加接收器线程池(brpc_num_threads)并确认 NIC 带宽和 QoS 设置。

启用传输压缩 – 当网络带宽成为瓶颈时,可压缩 Exchange 传输的数据以减少网络字节量。设置 SET transmission_compression_type = 'zstd';,并可选地将 SET transmission_encode_level = 7; 打开自适应列编码。需要权衡更高的 CPU 开销以换取更小的网络传输量。

2.5 排序 / 合并 / 窗口

为便于理解各类指标,可将 Merge 抽象为如下状态机:

               ┌────────── PENDING ◄──────────┐
│ │
│ │
├──────────────◄───────────────┤
│ │
▼ │
INIT ──► PREPARE ──► SPLIT_CHUNK ──► FETCH_CHUNK ──► FINISHED

|
| 一次从叶到根的遍历
|

PROCESS

排序溢出 – 当 MaxBufferedBytes 超过约 2 GB 或 SpillBytes 非零时,排序阶段发生磁盘溢出。可添加 LIMIT、在上游做预聚合,或在内存充足时提高会话 full_sort_max_buffered_bytes(以及/或 full_sort_max_buffered_rows)。

合并饥饿 – 高 PendingStageTime 表示合并正在等待上游块。首先优化生产者 operator 或扩大管道缓冲区。

宽窗口分区 – 窗口 operator 内巨大的 PeakBufferedRows 指向非常宽的分区或缺少帧限制的 ORDER BY。更细粒度地分区,添加 RANGE BETWEEN 边界,或物化中间聚合。


3 · 内存和溢出速查表

阈值关注点实际行动
80 % 的 BE 内存QueryPeakMemoryUsagePerNode降低会话 exec_mem_limit 或增加 BE 内存
检测到溢出(SpillBytes > 0)QuerySpillBytes,每个算子的 SpillBlocks提高内存上限;升级到 SR 3.2+ 以获得混合哈希/归并溢出

4 · 事后分析模板

1. 症状
– 慢阶段:Aggregate (OperatorTotalTime 68 %)
– 红旗:HashTableMemoryUsage 9 GB (> exec_mem_limit)
2. 根本原因
– GROUP BY 高基数 UUID
3. 应用的修复
– 添加排序流聚合 + 汇总 MV
4. 结果
– 查询运行时间从 95 s 降至 8 s;内存峰值 0.7 GB```
Rocky the happy otterStarRocks Assistant

AI generated answers are based on docs and other sources. Please test answers in non-production environments.