跳到主要内容

EXPLAIN

描述

EXPLAIN 语句用于展示 Doris 对于给定的查询所规划的查询计划。Doris 查询优化器的核心目标在于,针对任意给定的查询,生成一个高效且优化的执行计划。该优化器充分利用统计信息、数据特性以及 Doris 本身的功能优势,例如 HASH JOIN、分区和分桶等,来精心制定执行计划。然而,由于路径搜索的固有理论限制以及优化器实现过程中的实际情况,有时生成的执行计划可能无法达到预期的执行效果。为了进一步提升执行性能,我们的首要任务是深入分析优化器当前生成的执行计划。本文将介绍如何使用 EXPLAIN 语句,以便为后续的优化工作奠定坚实基础。

语法

{EXPLAIN | DESC} [VERBOSE] <query_block>

必选参数

<query_block>

需要查看执行计划的查询语句。

可选参数

[VERBOSE]

是否展示明细信息。当指定 VERBOSE 时,展示明细信息。否则,展示简略信息。详细信息包括每个算子上的详细信息,算子使用的 tuple 序号,以及对每个 tuple 的详细说明。

返回结果

基本概念

为了能够更好的理解 EXPLAIN 所展示的信息,这里先介绍几个 DORIS 执行计划的核心概念。

名称解释
PLAN执行计划,一个查询会被执行规划器翻译成一个执行计划,之后执行计划会提供给执行引擎执行。
FRAGMENT执行片段。由于 DORIS 是一个分布式执行引擎。一个完整的执行计划会被切分为多个单机的执行片段。一个 FRAGMENT 表是一个完整的单机执行片段。多个 FRAGMENT 组合在一起,构成一个完整的 PLAN。
PLAN NODE算子。执行计划的最小单位。一个 FRAGMENT 由多个算子构成。每一个算子负责一个实际的执行逻辑,比如聚合,连接等。

返回结果结构

Doris EXPLAIN 语句的结果是一个完整的 PLAN。PLAN 内部是按照执行顺序从后到前有序排列的 FRAGMENT。FRAGMENT 内部是按照执行顺序从后到前有序排列的算子(PLAN NODE)。

示例如下:

+--------------------------------------------------+
| Explain String(Nereids Planner) |
+--------------------------------------------------+
| PLAN FRAGMENT 0 |
| OUTPUT EXPRS: |
| cnt[#10] |
| cnt[#11] |
| PARTITION: UNPARTITIONED |
| |
| HAS_COLO_PLAN_NODE: false |
| |
| VRESULT SINK |
| MYSQL_PROTOCAL |
| |
| 7:VEXCHANGE |
| offset: 0 |
| distribute expr lists: |
| |
| PLAN FRAGMENT 1 |
| |
| PARTITION: RANDOM |
| |
| HAS_COLO_PLAN_NODE: false |
| |
| STREAM DATA SINK |
| EXCHANGE ID: 07 |
| UNPARTITIONED |
| |
| 6:VHASH JOIN(354) |
| | join op: INNER JOIN(BROADCAST)[] |
| | equal join conjunct: cnt[#7] = cnt[#5] |
| | cardinality=1 |
| | vec output tuple id: 8 |
| | vIntermediate tuple ids: 7 |
| | hash output slot ids: 5 7 |
| | distribute expr lists: |
| | distribute expr lists: |
| | |
| |----4:VEXCHANGE |
| | offset: 0 |
| | distribute expr lists: |
| | |
| 5:VEXCHANGE |
| offset: 0 |
| distribute expr lists: |
| |
| PLAN FRAGMENT 2 |
| ... |
| |
| PLAN FRAGMENT 3 |
| ... |
+--------------------------------------------------+

算子与其孩子节点之间,以虚线连接。当一个算子存在多个孩子时,孩子算子从上之下排布,表示从右至左的关系。以上面的示例为例。6 号算子 VHASH JOIN 的左孩子是 5 号 EXCHANGE 算子,右孩子是 4 号 EXCHANGE 算子。

Fragment 字段说明

名称解释
PARTITION展示当前 Fragment 的数据分布情况
HAS_COLO_PLAN_NODE当前 fragment 中是否存在 colocate 的算子
Sinkfragment 数据输出的方式,具体方式先下表

Sink 方式

名称解释
STREAM DATA SINK向下一个 Fragment 输出数据。这里主要包含两行信息。
第一行:数据发送给哪个下游的 EXCHANGE NODE 节点。
第二行:数据按照何种方式发送
- UNPARTITIONED 下游的每个 instance 都会获得全量的数据。这一般出现在两种情况下。一个是 broadcast join,另外一个是需要单 instance 计算的逻辑,比如全局的 limit,order by 等。
- RANDOM 下游的每个 instance 获得随机的一组数据,不同 instance 之间的数据不重复。
- HASH_PARTITIONED 以后续列出的 slot 为 key 做 hash,将同一个 hash 分片的数据发送到同一个下游的 instance 中。这多用于 partition hash join,两阶段聚合的第二阶段等算子的上游。
RESULT SINK向 FE 发送结果数据。第一行,表名发送数据采用的协议。现在有 MySQL 协议和 arrow 协议
OLAP TABLE SINK向 OLAP 表中写入数据
MultiCastDataSinks多发算子,下面包含多个 STREAM DATA SINK。每一个 STREAM DATA SINK 都发送全量的数据给下游。

Tuple 信息说明

在使用 VERBOSE 模式时,会输出 Tuple 信息。Tuple 信息描述了一行数据内的 SLOT 信息。包括 SLOT 的类型,nullable 等。

输出的信息包含多个 TupleDescriptor,每个 TupleDescriptor 中又包含多个 SlotDescriptor。示例如下:

Tuples:
TupleDescriptor{id=0, tbl=t1}
SlotDescriptor{id=0, col=c1, colUniqueId=0, type=int, nullable=true, isAutoIncrement=false, subColPath=null}
SlotDescriptor{id=2, col=c3, colUniqueId=2, type=int, nullable=true, isAutoIncrement=false, subColPath=null}

TupleDescriptor

名称解释
idtuple descriptor 的 id
tbltuple 对应的表,如果没有则留空

SlotDescriptor

名称解释
idslot descriptor 的 id
colslot 对应的列,如果没有则留空
colUniqueIdslot 对应的列的 unique id,如果没有则为 -1
typeslot 的类型
nullableslot 对应的数据是否可能为 null
isAutoIncrement是否是自增列
subColPath列中的子列路径,当前只应用于 variant 类型

算子说明

算子列表

名称解释
AGGREGATE聚合算子
ANALYTIC窗口函数算子
ASSERT NUMBER OF ROWS检查下游输出行数算子
EXCHANGE数据交换接收算子
MERGING-EXCHANGE带排序和限制输出行数功能的数据交换接收算子
HASH JOIN哈希连接算子
NESTED LOOP JOIN嵌套循环连接算子
PartitionTopN分组内数据预过滤算子
REPEAT_NODE数据重复生成算子
DataGenScanNode表值函数算子
EsScanNodeES 表扫描算子
HIVE_SCAN_NODEHive 表扫描算子
HUDI_SCAN_NODEHudi 表扫描算子
ICEBERG_SCAN_NODEIceberg 表扫描算子
PAIMON_SCAN_NODEPaimon 表扫描算子
JdbcScanNodeJdbc 表扫描算子
OlapScanNodeOlap 表扫描算子
SELECT过滤算子
UNION集合并集算子
EXCEPT集合差集算子
INTERSECT集合交集算子
SORT排序算子
TOP-N排序并返回前 N 个结果算子
TABLE FUNCTION NODE表函数算子(lateral view)

通用字段

名称解释
limit限制输出行数
offset输出前偏移的行数
conjuncts对于当前节点的结果做过滤。在 projections 前执行。
projections当前算子结束后,再进行的投影操作。在 conjuncts 后执行。
project output tuple id投影之后的输出 tuple,可以通过 tuple desc 看到具体的数据 tuple 内的 slot 排列
cardinality优化器预估的行数
distribute expr lists当前节点的孩子节点的原始数据分布方式
表达式的 slot idslot id 对应的具体 slot 可以在 verbose 模式中的 tuple 列表中找到。通过此列表,可以查看 slot 的类型和 nullable 属性等信息。表现形式是表达式后的[#5]

AGGREGATE

名称解释
(聚合阶段)聚合阶段有前后两个词表示。
第一个词有两个可选,update 和 merge。update 表示本地聚合。merge 表示 全局聚合。
第二个词表示当前数据是否被序列化。serialize 表示数据处在序列化的状态,finalize 表示已经完成最终计算。
STREAMING只有多阶段聚合的局部聚合算子截断有此标识。表示当前聚合节点可能使用 STREAMING 模式。即透不进行实际计算,直接将输入数据透传给下一阶段的聚合算子。
output当前聚合算子的输出。所有本地预聚合的函数,都会被冠以 partial 前缀
group by聚合的 key

ANALYTIC

名称解释
functions当前的窗口函数名字
partition by对应窗口函数中 over 后面的 partition by。开窗表达式。
order by窗内排序的表达式和排序方式
window窗口范围

ASSERT NUMBER OF ROWS

名称解释
EQ下游的输出必须满足等于此约束的行数

HASH JOIN

名称解释
join op连接的类型
equal join conjunct连接条件中的等值条件
other join predicates连接条件中,除等值条件外的其他条件
mark join predicatesmark join 所使用的条件
other predicates在 join 执行后的过滤谓词
runtime filters生成的 runtime filter
output slot ids最终输出的 slot 列表
hash output slot idshash 连接执行后,执行其他连接条件前,输出的 slot 列表
isMarkJoin是否为 mark join

NESTED LOOP JOIN

名称解释
join op连接的类型
join conjuncts连接的条件
mark join predicatesmark join 所使用的条件
predicates在 join 执行后的过滤谓词
runtime filters生成的 runtime filter
output slot ids最终输出的 slot 列表
isMarkJoin是否为 mark join

PartitionTopN

名称解释
functions应用分组过滤优化的窗口函数
has global limit是否有全局的 limit 行数限制
partition limit分组内的 limit 行数
partition topn phase当前阶段:TWO_PHASE_GLOBAL_PTOPN:global 阶段,数据按照 partition key shuffle 之后执行 TWO_PHASE_LOCAL_PTOPN:local 阶段,数据按照 partition key shuffle 之前执行

REPEAT_NODE

名称解释
repeat每一行数据会生成重复生成多少行,以及其对应的聚合列 slot id 列表
exprs数据重复后输出数据的表达式列表

DataGenScanNode

名称解释
table value function表函数名字

EsScanNode

名称解释
SORT COLUMN返回结果排序列
LOCAL_PREDICATES在 Doris 内执行的过滤条件
REMOTE_PREDICATES在 ES 内执行的过滤条件
ES index/type查询的 ES 的 index 和 type

HIVE_SCAN_NODE

名称解释
inputSplitNum扫描的分段数量
totalFileSize扫描的总文件大小
scanRanges扫描分段信息
partition扫描分区数
backends每个 BE 需要扫描的具体数据信息
cardinality优化器预估的扫描行数
avgRowSize优化器预估的每行数据平均大小
numNodes当前算子使用的 BE 数量
pushdown agg下压到扫描中的聚合计算

HUDI_SCAN_NODE

名称解释
inputSplitNum扫描的分段数量
totalFileSize扫描的总文件大小
scanRanges扫描分段信息
partition扫描分区数
backends每个 BE 需要扫描的具体数据信息
cardinality优化器预估的扫描行数
avgRowSize优化器预估的每行数据平均大小
numNodes当前算子使用的 BE 数量
pushdown agg下压到扫描中的聚合计算
hudiNativeReadSplits使用 native 方式读取的分片数量

ICEBERG_SCAN_NODE

名称解释
inputSplitNum扫描的分段数量
totalFileSize扫描的总文件大小
scanRanges扫描分段信息
partition扫描分区数
backends每个 BE 需要扫描的具体数据信息
cardinality优化器预估的扫描行数
avgRowSize优化器预估的每行数据平均大小
numNodes当前算子使用的 BE 数量
pushdown agg下压到扫描中的聚合计算
icebergPredicatePushdown下压给 iceberg api 的过滤条件

PAIMON_SCAN_NODE

名称解释
inputSplitNum扫描的分段数量
totalFileSize扫描的总文件大小
scanRanges扫描分段信息
partition扫描分区数
backends每个 BE 需要扫描的具体数据信息
cardinality优化器预估的扫描行数
avgRowSize优化器预估的每行数据平均大小
numNodes当前算子使用的 BE 数量
pushdown agg下压到扫描中的聚合计算
paimonNativeReadSplits使用 native 方式读取的分片数量

JdbcScanNode

名称解释
TABLE扫描的 JDBC 侧的表名
QUERY扫描使用的查询语句

OlapScanNode

名称解释
TABLE当前算子扫描的表。表名后面的括号,表明当前命中的同步物化视图的名字
SORT INFO规划出 SCAN 预排序时,有此字段。表明 SCAN 输出采用了局部预排序,和预截断。
SORT LIMIT规划出 SCAN 预排序时,有此字段。表明预截断的截断数据长度。
TOPN OPT规划出 TOP-N Runtime Filter 时,有此字段。
PREAGGREGATION是否开启预聚合,聚合模型和主键模型 MOR 需要关注此字段。当为 ON 时,表名存储层数据以满足上层需求,不需要执行额外的聚合操作。为 OFF 时,会执行额外的聚合操作。
partitions当前扫描的 PARTITION 个数,总 PARTITION 个数,以及扫描的 PARTITION 名字列表
tablets扫描的 TABLET 个数和表的总 TABLET 个数
tabletList扫描的 TABLET 的列表
avgRowSize优化器预估的每行数据大小
numNodes当前扫描被分配到的 BE 的个数
pushAggOp通过读取 zonemap 元数据返回结果。支持 MIN,MAX,COUNT 三个聚合信息

UNION

名称解释
constant exprs常量表达式列表,输出中将包含这些常量表达式
child exprs孩子的输出通过此表达式列表投影后,作为集合算子的输入

EXCEPT

名称解释
child exprs孩子的输出通过此表达式列表投影后,作为集合算子的输入

INTERSECT

名称解释
child exprs孩子的输出通过此表达式列表投影后,作为集合算子的输入

SORT

名称解释
order by排序的键,以及具体的排序顺序

TABLE FUNCTION NODE

名称解释
table function使用的 table function 的名字
lateral view tuple id新生成的列对应的 tuple 的 id
output slot id当前节点经过列裁剪后输出的列的 slot 列表

TOP-N

名称解释
order by排序的键,以及具体的排序顺序
TOPN OPT命中 topn runtime filter 优化时有此字段
OPT TWO PHASE命中 topn 延迟物化时,有此字段