大数据Doris之_10_查询加速_查询调优
前言
Github:https://github.com/HealerJean
一、调优概述
查询性能调优是一个系统工程,需要从多层次、多维度对数据库系统进行调优。以下是调优流程和方法论概述:
- 首先,业务人员和数据库管理员(
DBA)需要对所使用的数据库系统有全面的了解,包括业务系统使用的硬件、集群的规模、使用的数据库软件版本,以及具体软件版本所提供的特性等。 - 其次,一个好用的性能诊断工具是定位性能问题的必要前提。只有高效快速地定位到问题
SQL或者慢SQL,才能进行后续的具体性能调优流程。 - 在进入性能调优环节之后,一些常用的性能分析工具是必不可少的。这其中包括当前运行数据库系统自带的特有工具,以及操作系统层面的通用工具。
- 有了上述工具之后,使用特有工具可以获取
SQL运行在当前数据库系统上的详细信息,帮助定位性能瓶颈,同时,通用工具也可以作为辅助分析手段帮助定位问题。
1、诊断工具
高效好用的性能诊断工具对于数据库系统的调优至关重要,因为这取决于是否能快速定位到存在性能问题的业务
SQL,继而快速定位和解决性能瓶颈,保证数据库系统服务的SLA。当前,
Doris系统默认将执行时间超过5秒的SQL认定为慢SQL,此阈值可通过config.qe_slow_log_ms进行配置。目前Doris提供了以下三种诊断渠道,能够帮助快速定位存在性能问题的慢SQL,分别如下:
1)Doris Manager 日志
DorisManager的日志模块提供了慢SQL筛选功能。用户可以通过选择特定FE节点上的fe.audit.log来查看慢SQL。只需在搜索框中输入“slow_query”,即可在页面上展示当前系统的历史慢SQL信息,如下图所示:

2)Audit Log
当前
DorisFE提供了四种类型的AuditLog,包括slow_query、query、load和stream_load。AuditLog除了在安装部署Manager服务的集群上通过日志页面访问获取之外,也可以直接访问FE所在节点的fe/log/fe.audit.log文件获取信息。
通过直查 fe.audit.log 中的 slow_query 标签,可以快速筛选出执行缓慢的查询 SQL。
通过 fe.audit.log 获取的慢 SQL,使用者可以方便地获取执行时间、扫描行数、返回行数、SQL 语句等详细信息,为进一步重现和定位性能问题奠定了基础。
3)audit_log 系统表
audit_log系统表:Doris2.1以后的版本在__internal_schema数据库下提供了audit_log系统表,供用户查看SQL运行的情况。使用前需要打开全局配置set global enable_audit_plugin=true;(此开关默认关闭)
ysql> use __internal_schema;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+-----------------------------+
| Tables_in___internal_schema |
+-----------------------------+
| audit_log |
| column_statistics |
| histogram_statistics |
| partition_statistics |
+-----------------------------+
4 rows in set (0.00 sec)
mysql> desc audit_log;
+-------------------+--------------+------+-------+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+--------------+------+-------+---------+-------+
| query_id | varchar(48) | Yes | true | NULL | |
| time | datetime | Yes | true | NULL | |
| client_ip | varchar(128) | Yes | true | NULL | |
| user | varchar(128) | Yes | false | NULL | NONE |
| catalog | varchar(128) | Yes | false | NULL | NONE |
| db | varchar(128) | Yes | false | NULL | NONE |
| state | varchar(128) | Yes | false | NULL | NONE |
| error_code | int | Yes | false | NULL | NONE |
| error_message | text | Yes | false | NULL | NONE |
| query_time | bigint | Yes | false | NULL | NONE |
| scan_bytes | bigint | Yes | false | NULL | NONE |
| scan_rows | bigint | Yes | false | NULL | NONE |
| return_rows | bigint | Yes | false | NULL | NONE |
| stmt_id | bigint | Yes | false | NULL | NONE |
| is_query | tinyint | Yes | false | NULL | NONE |
| frontend_ip | varchar(128) | Yes | false | NULL | NONE |
| cpu_time_ms | bigint | Yes | false | NULL | NONE |
| sql_hash | varchar(128) | Yes | false | NULL | NONE |
| sql_digest | varchar(128) | Yes | false | NULL | NONE |
| peak_memory_bytes | bigint | Yes | false | NULL | NONE |
| stmt | text | Yes | false | NULL | NONE |
+-------------------+--------------+------+-------+---------+-------+
2、分析工具
上节[诊断工具]已经帮助业务和运维人员定位到具体的慢
SQL,本章节开始介绍如何对慢SQL的性能瓶颈进行分析,以确定具体慢在SQL执行的哪个环节。
一条 SQL 的执行过程大致可以分为计划生成和计划执行两个阶段,前一部分负责生成执行计划,后一部分负责具体计划的执行。
-
性能瓶颈
-
计划生成问题:这两个部分出现问题都可能导致性能瓶颈的发生。比如生成了差计划,那么即使再优秀的执行器也不可能获得很好的性能。
- 执行手段问题:同样一个正确的计划,如果相应的执行手段不合适,也容易产生性能瓶颈。
- 技术设施问题:此外,执行器的性能和当前运行的硬件和系统架构有紧密的关系,一些基础设施的缺陷或者配置不正确也会导致性能问题。
-
上述三类问题都需要良好的分析工具的支持。基于此,Doris 系统提供了两个性能分析工具来分别分析计划以及执行的性能瓶颈。另外系统级别也提供了相应的性能检测工具,辅助定位性能瓶颈。下面分别就这三个方面进行介绍:
二、计划生成: Explain
执行计划是对一条
SQL具体的执行方式和执行过程的描述。例如,对于一个两表连接的SQL,执行计划会展示这两张表的访问方式信息、连接方式信息,以及连接的顺序等。Doris提供了Explain工具,可以方便的展示一个SQL的执行计划的详细信息。通过对Explain输出的计划进行分析,可以帮助使用者快速定位计划层面的瓶颈,从而针对不同的情况进行计划层面的调优。
调优原则:
-
从抽象到具体:先通过
SHAPE PLAN确认整体结构,再逐步深入细节。 -
从逻辑到物理:先用
ALL PLAN和MEMO PLAN分析逻辑优化,再用VERBOSE调试物理执行。 -
从成本到资源:通过
ALL PLAN比较计划成本,再通过VERBOSE检查资源分配(如内存、线程)。
| 顺序 | 工具 | 核心目标 | 典型问题定位 |
|---|---|---|---|
| 1 | EXPLAIN SHAPE PLAN |
快速理解查询逻辑架构,快速定位整体结构 | 错误的 Join 顺序、缺失的聚合操作符 |
| 2 | EXPLAIN ALL PLAN |
比较备选方案,验证优化器选择是否合理 | 过滤条件下推失败、备选计划成本异常 |
| 3 | EXPLAIN MEMO PLAN |
调试优化器规则 | 预期规则未触发、规则应用顺序错误 |
| 4 | EXPLAIN VERBOSE |
检查操作符物理执行参数,调试底层细节 | 内存不足、线程竞争、Join 策略不合理 |
1、工具介绍
1)EXPLAIN SHAPE PLAN(快速定位整体结构)
-
目的:快速理解查询的逻辑架构,确认关键操作符(如
JOIN、AGGREGATE)是否存在明显问题。 -
适用阶段:初步分析,排除结构性错误(如错误的
Join顺序)。
2)EXPLAIN ALL PLAN(比较备选方案)
- 目的:验证优化器选择的计划是否合理,分析备选方案被放弃的原因(如成本过高)。
- 适用阶段:确认优化器行为是否符合预期,发现潜在优化机会。
3)EXPLAIN MEMO PLAN(调试优化器规则)
- 目的:深入分析优化器内部的规则匹配和转换过程,定位规则未生效的原因。
- 适用阶段:优化器行为异常时(如预期的规则未触发)。
4)EXPLAIN VERBOSE(调试底层细节)
- 目的:检查操作符的物理执行参数(如内存分配、线程模型),解决底层性能问题。
- 适用阶段:针对具体操作符(如
HashJoin)进行深度调优。
2、调优举例
1)案例1
SELECT d.dept_name, AVG(e.salary)
FROM departments d
JOIN employees e ON d.id = e.dept_id
WHERE e.hire_date > '2020-01-01'
GROUP BY d.dept_name;
a、使用 EXPLAIN SHAPE PLAN 快速定位结构
EXPLAIN SHAPE PLAN
SELECT d.dept_name, AVG(e.salary)
FROM departments d
JOIN employees e ON d.id = e.dept_id
WHERE e.hire_date > '2020-01-01'
GROUP BY d.dept_name;
ROOT: HashAggregate
|-- HashJoin(condition=[id = dept_id])
| |-- Scan(departments)
| +-- Scan(employees)
+-- GroupBy(dept_name)
- 分析:
- 确认查询包含
HashJoin和HashAggregate两个关键操作符。 - 检查
Scan(employees)是否在HashJoin之前,可能存在过滤条件下推问题。
- 确认查询包含
b、使用 EXPLAIN ALL PLAN 比较备选方案
EXPLAIN ALL PLAN
SELECT d.dept_name, AVG(e.salary)
FROM departments d
JOIN employees e ON d.id = e.dept_id
WHERE e.hire_date > '2020-01-01'
GROUP BY d.dept_name;
Plan 1 (cost=1000): # 优化器选择的计划
- HashJoin: d.id = e.dept_id
- Scan(departments): full scan
- Scan(employees): filtered by hire_date > '2020-01-01'
Plan 2 (cost=2000): # 被放弃的备选方案
- HashJoin: d.id = e.dept_id
- Scan(departments): full scan
- Scan(employees): full scan + Filter hire_date > '2020-01-01'
- 分析:
- 优化器正确选择了
Plan 1,将过滤条件hire_date > '2020-01-01'下推到employees表的扫描阶段。 - 若输出显示
Plan 2被选中,则说明过滤条件下推失败,需进一步调试。
- 优化器正确选择了
c、使用 EXPLAIN MEMO PLAN 调试优化器规则
EXPLAIN MEMO PLAN
SELECT d.dept_name, AVG(e.salary)
FROM departments d
JOIN employees e ON d.id = e.dept_id
WHERE e.hire_date > '2020-01-01'
GROUP BY d.dept_name;
Memo Group 0:
- LogicalAggregate(groupBy=[dept_name], avg=[salary])
- LogicalJoin(condition=[id=dept_id])
- LogicalScan(departments)
- LogicalScan(employees)
Applied Rules:
- PushFilterThroughJoin: Success (hire_date > '2020-01-01' pushed to employees)
- MergeAdjacentAggregates: Failed (no adjacent aggregates)
- 分析:
- 确认规则
PushFilterThroughJoin成功应用,过滤条件已下推。 - 若规则未生效,需检查表统计信息或优化器配置。
- 确认规则
d、使用 EXPLAIN VERBOSE 调试底层细节
EXPLAIN VERBOSE
SELECT d.dept_name, AVG(e.salary)
FROM departments d JOIN employees e ON d.id = e.dept_id
WHERE e.hire_date > '2020-01-01'
GROUP BY d.dept_name;
- HashJoin: d.id = e.dept_id (BuildSide=departments, ProbeSide=employees)
- OlapScanNode(departments): tablets=10, memory_usage = 128MB
- OlapScanNode(employees): tablets=100, filtered by hire_date > '2020-01-01', memory_usage=512MB
- 分析:
- 确认
HashJoin的构建侧(BuildSide)和探测侧(ProbeSide)分配合理。 - 检查
employees表的扫描内存(memory_usage)是否足够,避免溢出到磁盘。
- 确认
三、计划执行:Profile
当
SQL具体执行时,如何了解每个具体的执行分别耗费多少时间,比如build耗费多少时间,probe耗费多少时间,profile工具提供了详细的执行信息供性能分析和调优使用。下面部分先整体介绍Profile的文件结构,然后分别介绍MergedProfile,ExecutionProfile以及PipelineTask的执行时间含义:
1、Profile 文件结构
Profile 文件中包含几个主要的部分:
-
查询基本信息:包括
ID,时间,数据库等。-
Query ID: 123456789 Start Time: 2025-07-08 12:00:00 Database: sales_db
-
-
SQL语句以及执行计划:展示原始SQL及优化后的物理执行计划(如HashJoin、Aggregate等操作符的树状结构)-
SQL: SELECT d.dept_name, AVG(e.salary) FROM departments d JOIN employees e ON d.id = e.dept_id GROUP BY d.dept_name; Plan: ROOT: HashAggregate |-- HashJoin(condition=[ id = dept_id]) | |-- Scan(departments) | +-- Scan(employees) +-- GroupBy(dept_name)
-
-
FE的耗时(PlanTime,ScheduleTime等):-
PlanTime计划生成时间,数据库优化器为查询生成执行计划所花费的时间 -
ScheduleTime调度执行时间,从执行计划开始执行到返回结果的总时间 -
FE Timing: Plan Time: 15ms Schedule Time: 5ms
-
-
BE在执行过程中各个operator的执行耗时(包括MergedProfile和ExecutionProfile)
2、Merged Profile:操作符聚合指标)
MergedProfile,对每个Operator操作符的并发执行实例进行指标聚合,提供以下核心字段:
| 指标名称 | 指标含义 | 说明 |
|---|---|---|
BlocksProduced |
产生的 Data Block 数量 |
|
CloseTime |
Operator 在 close 阶段的耗时 |
|
ExecTime |
Operator 在各个阶段执行的总耗时 |
|
InitTime |
Operator 在 Init 阶段的耗时 |
|
MemoryUsage |
Operator 在执行阶段的内存用量 |
如果内存使用峰值较高,需关注是否触发磁盘溢出(Spill)。 |
OpenTime |
Operator 在 Open 阶段的耗时 |
|
ProjectionTime |
Operator 在做 projection 的耗时 |
|
RowsProduced |
Operator 返回的行数 |
|
WaitForDependencyTime |
Operator 等待自身执行的条件依赖的时间(如 EXCHANGE_OPERATOR 等待 RPC 数据)。 |
该操作符耗时主要在等待数据,可能存在网络瓶颈或上游算子性能问题。 |
EXCHANGE_OPERATOR (id=4):
- BlocksProduced: sum 0, avg 0, max 0, min 0
- CloseTime: avg 34.133us, max 38.287us, min 29.979us
- ExecTime: avg 700.357us, max 706.351us, min 694.364us
- InitTime: avg 648.104us, max 648.604us, min 647.605us
- MemoryUsage: sum , avg , max , min
- PeakMemoryUsage: sum 0.00 , avg 0.00 , max 0.00 , min 0.00
- OpenTime: avg 4.541us, max 5.943us, min 3.139us
- ProjectionTime: avg 0ns, max 0ns, min 0ns
- RowsProduced: sum 0, avg 0, max 0, min 0
- WaitForDependencyTime: avg 0ns, max 0ns, min 0ns
- WaitForData0: avg 9.434ms, max 9.476ms, min 9.391ms
3、Execution Profile:单并发实例详情
区别于
MergedProfile,ExecutionProfile展示单个并发实例的详细指标,适用于定位数据倾斜或单实例性能问题。以 id=4 的这个exchangeoperator为例:
示例:EXCHANGE_OPERATOR 的 Execution Profile
分析:
- 该实例处理了
50万行数据,耗时706μs,无显著等待时间。 - 若其他实例的
ExecTime显著高于此值,可能存在数据倾斜。
EXCHANGE_OPERATOR (id=4):(ExecTime: 706.351us)
- BlocksProduced: 0
- CloseTime: 38.287us
- DataArrivalWaitTime: 0ns
- DecompressBytes: 0.00
- DecompressTime: 0ns
- DeserializeRowBatchTimer: 0ns
- ExecTime: 706.351us
- FirstBatchArrivalWaitTime: 0ns
- InitTime: 647.605us
- LocalBytesReceived: 512MB
- MemoryUsage:
- PeakMemoryUsage: 0.00
- OpenTime: 5.943us
- ProjectionTime: 0ns
- RemoteBytesReceived: 0.00
- RowsProduced: 500000
- SendersBlockedTotalTimer(*): 0ns
- WaitForDependencyTime: 0ns
- WaitForData0: 9.476ms
4、PipelineTask :并行任务调度
在
Doris中,执行计划会拆分为多个PipelineTask,1个PipelineTask由多个operator组成。
Fragment层:FE将查询计划拆分为多个Fragment(每个Fragment是一个子计划,可能跨节点)。- 例如:
Map Fragment(数据扫描)和Reduce Fragment(聚合/Join)。
Pipeline层:- 每个
Fragment进一步拆分为多个Pipeline(基于数据依赖关系)。 - 例如:
Map Fragment可能拆分为ScanPipeline和ProjectPipeline。
- 每个
Task层:- 每个
Pipeline拆分为多个PipelineTask(基于并行度pipeline_dop)。 - 例如:
ScanPipeline拆分为 4 个Task,每个Task处理一个数据分片。
- 每个
| 关键指标 | 解释 | 说明 | 使用 |
|---|---|---|---|
ExecuteTime: |
实际执行时间 | PipelineTask 中所有 Operator 的 ExecTime 之和 |
反映任务实际处理数据的耗时。 |
WaitWorkerTime |
等待 Worker 时间 |
task 处于 runnable 状态时,他要等待一个空闲 worker 来执行,这个耗时主要取决于集群负载 |
等待可用工作线程的时间,反映了系统资源竞争程度。 |
WaitForDependencyTime |
等待依赖时间 | Task 等待上游 Operator 输出数据或系统资源(如 Buffer、RPC)的时间 |
依赖等待时间远高于实际执行时间,说明 上游数据生产是瓶颈。 |
案例-现象
PipelineTask总耗时 15ms,其中:ExecuteTime: 3msWaitWorkerTime: 50usWaitForDependencyTime: 12ms(主要由EXCHANGE_OPERATOR的 RPC 等待导致)
根因分析
- 上游数据倾斜:某些
BE节点处理慢,导致数据分片延迟。 - 网络瓶颈:跨节点数据交换(
Shuffle)时网络拥塞。 - 资源不足:上游
Operator的并发度(parallel_fragment_exec_instance_num)设置过低。
四、调优流程
1、使用性能诊断工具进行慢查询定位
针对运行在 Doris 上的业务系统,使用上述性能 诊断工具 进行慢 SQL 的定位。
-
如果已经安装了
DorisManager,推荐使用Manager日志页面,方便的进行可视化的慢查询定位。 -
如果没有安装
Manager,可以直查 FE 节点上的fe.audit.log或者audit_log系统表来获取慢SQL列表,按优先级进行调优。
2、Schema 设计与调优
定位到具体的慢 SQL 之后,优先需要对业务 Schema 设计进行检查与调优,排除因为 Schema 设计不合理导致的性能问题。
Schema 设计调优基本可分为三个方面:
- 表级别
Schema设计调优:如分区分桶个数和字段调优; - 索引的设计和调优;
- 特定优化手段的使用,如使用
ColocateGroup优化Join等。主要目的是排除因为Schema设计不合理或者没有充分利用Doris现有优化能力导致的性能问题。
3、计划调优
检查和优化完业务 Schema 后,将进入调优的主体工作,即计划调优与执行调优。如上所述,在性能调优工具中,这个阶段的主要工作是充分利用 Doris 所提供的各种层级的 Explain 工具,对慢 SQL 的执行计划进行系统分析,以找到关键优化点进行针对性优化。
4、执行调优
进入执行调优阶段后,需要根据 SQL 的实际运行情况,一方面验证计划调优的效果,另外一方面在现有计划的前提下,继续分析执行侧的瓶颈点,定位哪个执行阶段慢,或者其他普遍性的原因,如并行度不优等。


