跳到主要内容
版本: 最新版本-3.5

查询调优方法

一个实用的手册:症状 → 根本原因 → 验证过的修复方法
当您打开一个 Profile 并发现一个危险指标,但仍然需要回答“接下来怎么办?”时,请使用它。


1 · 快速诊断工作流程

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

  2. 找到最慢的 Pipeline / Operator
    ⟶ 在查询 Profile UI 中,点击按 OperatorTotalTime % 排序
    最热的 operator 告诉您接下来要阅读哪个秘籍块(Scan, Join, Aggregate, …)。

  3. 确认瓶颈子类型
    每个秘籍都以其签名指标模式开始。在尝试修复之前,请匹配这些模式。


2 · 按 Operator 分类秘籍

2.1 OLAP / Connector Scan [指标]

为了更好地理解 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,启用存储缓存,或者——如果您正在扫描 S3/HDFS——提高 remote_cache_capacity

缺少 Filter push‑down – 如果 PushdownPredicates 保持在 0 附近,而 ExprFilterRows 很高,则谓词无法到达存储层。将它们重写为简单的比较(避免 %LIKE% 和宽 OR 链)或添加 zonemap/Bloom 索引或物化视图,以便可以将其下推。

线程池饥饿 – 高 IOTaskWaitTime 以及低 PeakIOTasks 表明 I/O 线程池已饱和。增加 BE 上的 max_io_threads 或扩大缓存以允许更多任务并发运行。

平板之间的数据倾斜 – 最大 OperatorTotalTime 和最小 OperatorTotalTime 之间的差距很大意味着某些平板比其他平板做更多的工作。在更高基数的键上重新分桶或增加桶计数以分散负载。

Rowset/segment 碎片 – 爆炸性的 RowsetsReadCount/SegmentsReadCount 加上长时间的 SegmentInitTime 表明存在许多微小的 rowset。触发手动 compaction 并批量加载小负载,以便段可以预先合并。

累积的软删除 – 大量的 DeleteFilterRows 意味着大量使用软删除。运行 BE compaction 以清除墓碑并整合删除位图。

2.2 Aggregate [指标]

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

聚合算法的多种形式

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

多阶段分布式聚合

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

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

常见的性能瓶颈

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

Shuffle 倾斜 – 片段之间 HashTableSizeInputRowCount 的巨大差异揭示了不平衡的 shuffle。向键添加 salt 列或使用 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 [指标]

join_operator

Join Operator 负责实现显式 join 或隐式 join。

在执行期间,join operator 分为 Build(哈希表构建)和 Probe 阶段,这些阶段在 pipeline 引擎内并行运行。向量块(最多 4096 行)使用 SIMD 进行批量哈希;消耗的键生成运行时过滤器——Bloom 或 IN 过滤器——被推回到上游扫描以尽早削减 probe 输入。

Join 策略

StarRocks 依赖于矢量化、pipeline 友好的哈希连接核心,该核心可以连接到四种物理策略,基于成本的优化器会在计划时权衡这些策略

策略优化器何时选择它什么使其快速
Colocate Join两个表都属于同一个 colocation 组(相同的 bucket 键、bucket 计数和副本布局)。没有网络 shuffle:每个 BE 仅连接其本地 bucket。
Bucket-Shuffle Join其中一个 join 表具有与 join 键相同的 buckket 键只需要 shuffle 一个 join 表,这可以降低网络成本
Broadcast JoinBuild 侧非常小(行/字节阈值或显式提示)。小表被复制到每个 probe 节点;避免 shuffle 大表。
Shuffle (Hash) Join一般情况,键不对齐。在 join 键上哈希分区每一行,以便 probe 在 BE 之间平衡。

常见的性能瓶颈

过大的 build 侧BuildHashTableTimeHashTableMemoryUsage 的峰值显示 build 侧已超出内存。交换 probe/build 表,预先过滤 build 表,或启用哈希溢出。

缓存不友好的 probe – 当 SearchHashTableTime 占主导地位时,probe 侧的缓存效率不高。按 join 键对 probe 行进行排序并启用运行时过滤器。

Shuffle 倾斜 – 如果单个片段的 ProbeRows 比所有其他片段都多,则数据倾斜。切换到更高基数的键或附加 salt,例如 key || mod(id, 16)

意外的广播 – 带有巨大 BytesSent 的 Join 类型 BROADCAST 意味着您认为很小的表不是。降低 broadcast_row_limit 或使用 SHUFFLE 提示强制 shuffle。

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

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

2.4 Exchange (Network) [指标]

过大的 shuffle 或广播 – 如果 NetworkTime 超过 30 % 并且 BytesSent 很大,则查询正在传输过多的数据。重新评估 join 策略或启用 exchange compaction (pipeline_enable_exchange_compaction)。

接收器积压 – sink 中较高的 WaitTime 和保持满状态的发送者队列表明接收器无法跟上。增加接收器线程池 (brpc_num_threads) 并确认 NIC 带宽和 QoS 设置。

2.5 Sort / Merge / Window

为了便于理解各种指标,Merge 可以表示为以下状态机制

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

|
| one traverse from leaf to root
|

PROCESS

排序溢出 – 当 MaxBufferedBytes 升至大约 2 GB 以上或 SpillBytes 非零时,排序阶段将溢出到磁盘。添加 LIMIT,预先聚合上游,或者如果机器有足够的内存,则提高 sort_spill_threshold

合并饥饿 – 高 PendingStageTime 告诉您合并正在等待上游块。首先优化生产者 operator 或扩大 pipeline 缓冲区。

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


3 · 内存和溢出备忘单

阈值要观察什么实际行动
80 % 的 BE 内存QueryPeakMemoryUsagePerNode降低会话 exec_mem_limit 或添加 BE RAM
检测到溢出 (SpillBytes > 0)QuerySpillBytes,每个 operator 的 SpillBlocks增加内存限制;升级到 SR 3.2+ 以获得混合哈希/合并溢出

4 · 您的事后剖析模板

1. Symptom
– Slow stage: Aggregate (OperatorTotalTime 68 %)
– Red-flag: HashTableMemoryUsage 9 GB (> exec_mem_limit)
2. Root cause
– GROUP BY high-cardinality UUID
3. Fix applied
– Added sorted streaming agg + roll-up MV
4. Outcome
– Query runtime ↓ from 95 s ➜ 8 s; memory peak 0.7 GB```