EXPLAIN
描述
EXPLAIN 语句用于展示 PALO 对于给定的查询所规划的查询计划。PALO 查询优化器的核心目标在于,针对任意给定的查询,生成一个高效且优化的执行计划。该优化器充分利用统计信息、数据特性以及 PALO 本身的功能优势,例如 HASH JOIN、分区和分桶等,来精心制定执行计划。然而,由于路径搜索的固有理论限制以及优化器实现过程中的实际情况,有时生成的执行计划可能无法达到预期的执行效果。为了进一步提升执行性能,我们的首要任务是深入分析优化器当前生成的执行计划。本文将介绍如何使用 EXPLAIN 语句,以便为后续的优化工作奠定坚实基础。
语法
1{EXPLAIN | DESC} [VERBOSE] <query_block>
必选参数
<query_block>
需要查看执行计划的查询语句。
可选参数
[VERBOSE]
是否展示明细信息。当指定 VERBOSE 时,展示明细信息。否则,展示简略信息。详细信息包括每个算子上的详细信息,算子使用的 tuple 序号,以及对每个 tuple 的详细说明。
返回结果
基本概念
为了能够更好的理解 EXPLAIN 所展示的信息,这里先介绍几个 PALO 执行计划的核心概念。
| 名称 |
解释 |
| PLAN |
执行计划,一个查询会被执行规划器翻译成一个执行计划,之后执行计划会提供给执行引擎执行。 |
| FRAGMENT |
执行片段。由于 PALO 是一个分布式执行引擎。一个完整的执行计划会被切分为多个单机的执行片段。一个 FRAGMENT 表是一个完整的单机执行片段。多个 FRAGMENT 组合在一起,构成一个完整的 PLAN。 |
| PLAN NODE |
算子。执行计划的最小单位。一个 FRAGMENT 由多个算子构成。每一个算子负责一个实际的执行逻辑,比如聚合,连接等。 |
返回结果结构
PALO EXPLAIN 语句的结果是一个完整的 PLAN。PLAN 内部是按照执行顺序从后到前有序排列的 FRAGMENT。FRAGMENT 内部是按照执行顺序从后到前有序排列的算子(PLAN NODE)。
示例如下:
1+--------------------------------------------------+
2| Explain String(Nereids Planner) |
3+--------------------------------------------------+
4| PLAN FRAGMENT 0 |
5| OUTPUT EXPRS: |
6| cnt[#10] |
7| cnt[#11] |
8| PARTITION: UNPARTITIONED |
9| |
10| HAS_COLO_PLAN_NODE: false |
11| |
12| VRESULT SINK |
13| MYSQL_PROTOCAL |
14| |
15| 7:VEXCHANGE |
16| offset: 0 |
17| distribute expr lists: |
18| |
19| PLAN FRAGMENT 1 |
20| |
21| PARTITION: RANDOM |
22| |
23| HAS_COLO_PLAN_NODE: false |
24| |
25| STREAM DATA SINK |
26| EXCHANGE ID: 07 |
27| UNPARTITIONED |
28| |
29| 6:VHASH JOIN(354) |
30| | join op: INNER JOIN(BROADCAST)[] |
31| | equal join conjunct: cnt[#7] = cnt[#5] |
32| | cardinality=1 |
33| | vec output tuple id: 8 |
34| | vIntermediate tuple ids: 7 |
35| | hash output slot ids: 5 7 |
36| | distribute expr lists: |
37| | distribute expr lists: |
38| | |
39| |----4:VEXCHANGE |
40| | offset: 0 |
41| | distribute expr lists: |
42| | |
43| 5:VEXCHANGE |
44| offset: 0 |
45| distribute expr lists: |
46| |
47| PLAN FRAGMENT 2 |
48| ... |
49| |
50| PLAN FRAGMENT 3 |
51| ... |
52+--------------------------------------------------+
算子与其孩子节点之间,以虚线连接。当一个算子存在多个孩子时,孩子算子从上之下排布,表示从右至左的关系。以上面的示例为例。6 号算子 VHASH JOIN 的左孩子是 5 号 EXCHANGE 算子,右孩子是 4 号 EXCHANGE 算子。
Fragment 字段说明
| 名称 |
解释 |
| PARTITION |
展示当前 Fragment 的数据分布情况 |
| HAS_COLO_PLAN_NODE |
当前 fragment 中是否存在 colocate 的算子 |
| Sink |
fragment 数据输出的方式,具体方式先下表 |
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。示例如下:
1Tuples:
2TupleDescriptor{id=0, tbl=t1}
3 SlotDescriptor{id=0, col=c1, colUniqueId=0, type=int, nullable=true, isAutoIncrement=false, subColPath=null}
4 SlotDescriptor{id=2, col=c3, colUniqueId=2, type=int, nullable=true, isAutoIncrement=false, subColPath=null}
TupleDescriptor
| 名称 |
解释 |
| id |
tuple descriptor 的 id |
| tbl |
tuple 对应的表,如果没有则留空 |
SlotDescriptor
| 名称 |
解释 |
| id |
slot descriptor 的 id |
| col |
slot 对应的列,如果没有则留空 |
| colUniqueId |
slot 对应的列的 unique id,如果没有则为 -1 |
| type |
slot 的类型 |
| nullable |
slot 对应的数据是否可能为 null |
| isAutoIncrement |
是否是自增列 |
| subColPath |
列中的子列路径,当前只应用于 variant 类型 |
算子说明
算子列表
| 名称 |
解释 |
| AGGREGATE |
聚合算子 |
| ANALYTIC |
窗口函数算子 |
| ASSERT NUMBER OF ROWS |
检查下游输出行数算子 |
| EXCHANGE |
数据交换接收算子 |
| MERGING-EXCHANGE |
带排序和限制输出行数功能的数据交换接收算子 |
| HASH JOIN |
哈希连接算子 |
| NESTED LOOP JOIN |
嵌套循环连接算子 |
| PartitionTopN |
分组内数据预过滤算子 |
| REPEAT_NODE |
数据重复生成算子 |
| DataGenScanNode |
表值函数算子 |
| EsScanNode |
ES 表扫描算子 |
| HIVE_SCAN_NODE |
Hive 表扫描算子 |
| HUDI_SCAN_NODE |
Hudi 表扫描算子 |
| ICEBERG_SCAN_NODE |
Iceberg 表扫描算子 |
| PAIMON_SCAN_NODE |
Paimon 表扫描算子 |
| JdbcScanNode |
Jdbc 表扫描算子 |
| OlapScanNode |
Olap 表扫描算子 |
| 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 id |
slot 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 predicates |
mark join 所使用的条件 |
| other predicates |
在 join 执行后的过滤谓词 |
| runtime filters |
生成的 runtime filter |
| output slot ids |
最终输出的 slot 列表 |
| hash output slot ids |
hash 连接执行后,执行其他连接条件前,输出的 slot 列表 |
| isMarkJoin |
是否为 mark join |
NESTED LOOP JOIN
| 名称 |
解释 |
| join op |
连接的类型 |
| join conjuncts |
连接的条件 |
| mark join predicates |
mark 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 |
在 PALO 内执行的过滤条件 |
| 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 延迟物化时,有此字段 |
![]()