分析查询计划
学习如何读取和分析查询计划以理解查询执行步骤和数据组织,并发现性能瓶颈。
当你查询 InfluxDB 3 时,查询器会为执行查询制定查询计划。引擎试图确定查询结构和数据的最优计划。通过学习如何生成和解释查询计划的报告,你可以更好地理解查询是如何执行的,并识别影响查询性能的瓶颈。
例如,如果查询计划揭示您的查询读取大量Parquet文件,您可以采取措施优化您的查询,例如添加过滤器以读取更少的数据。
使用 EXPLAIN 关键字查看查询计划
使用 EXPLAIN 关键字(以及可选的 ANALYZE 和 VERBOSE 关键字)查看查询的查询计划。
阅读EXPLAIN报告
当你 使用 EXPLAIN 关键字查看查询计划 时,报告包含以下内容:
读取查询计划
计划采用 树格式——每个计划都是一个倒置的树,其中执行和数据从 叶节点(计划中的最内层步骤)流向外部 分支节点。无论是阅读逻辑计划还是物理计划,都请牢记以下几点:
- 从叶节点开始,向上读取。
- 在计划的顶部,根节点表示最终的、包罗万象的执行步骤。
在一个 物理计划 中,每个步骤是一个 ExecutionPlan 节点,它接收输入数据和输出要求的表达式,并计算数据的划分。
使用以下步骤来分析查询计划并估计完成查询所需的工作量。无论计划看起来多大或多复杂,这些步骤都是适用的。
- 从最远的缩进步骤(叶节点)开始,向上阅读。
- 理解每个
ExecutionPlan节点 的工作–例如,一个UnionExec节点包含叶节点意味着UnionExec连接所有叶节点的输出。 - 对于每个表达式,回答以下问题:
- 输入到计划的数据的形状和大小是什么?
- 从计划输出的数据的形状和大小是什么?
本指南的其余部分将引导您分析物理计划。理解查询计划中节点的顺序、角色、输入和输出可以帮助您估计整体工作负载并找到查询中的潜在瓶颈。
SELECT - ORDER BY 查询的示例物理计划
以下示例显示了如何读取 EXPLAIN 报告和物理查询计划。
给定 h20 测量数据和以下查询:
EXPLAIN SELECT city, min_temp, time FROM h2o ORDER BY city ASC, time DESC;
输出类似于以下内容:
解释报告
| plan_type | plan |
+---------------+--------------------------------------------------------------------------+
| logical_plan | Sort: h2o.city ASC NULLS LAST, h2o.time DESC NULLS FIRST |
| | TableScan: h2o projection=[city, min_temp, time] |
| physical_plan | SortPreservingMergeExec: [city@0 ASC NULLS LAST,time@2 DESC] |
| | UnionExec |
| | SortExec: expr=[city@0 ASC NULLS LAST,time@2 DESC] |
| | ParquetExec: file_groups={...}, projection=[city, min_temp, time] |
| | SortExec: expr=[city@0 ASC NULLS LAST,time@2 DESC] |
| | ParquetExec: file_groups={...}, projection=[city, min_temp, time] |
| | |
来自 EXPLAIN SELECT city, min_temp, time FROM h2o ORDER BY city ASC, time DESC; 的输出
物理计划中的每一步,或称为 节点,都是一个 ExecutionPlan 名称和包含查询相关部分的键值 表达式——例如,EXPLAIN 报告 物理计划中的第一个节点是一个 ParquetExec 执行计划:
ParquetExec: file_groups={...}, projection=[city, min_temp, time]
因为 ParquetExec 和 RecordBatchesExec 节点在 InfluxDB 查询中检索和扫描数据,每个查询计划都以一个或多个这些节点开始。
物理计划数据流
数据在查询计划中 向上 流动。
下图展示了EXPLAIN报告物理计划中的数据流和节点顺序:
SortPreservingMergeExecUnionExecSortExecParquetExecSortExecParquetExec在EXPLAIN报告的物理计划中执行和数据流动。ParquetExec 节点并行执行,UnionExec 合并它们的输出。
以下步骤总结了物理计划执行和数据流:
- 两个
ParquetExec计划并行读取来自 Parquet 文件的数据:- 每个
ParquetExec节点处理一个或多个 文件组。 - 每个文件组包含一个或多个 Parquet 文件路径。
- 一个
ParquetExec节点并行处理其组,顺序读取每个组的文件。 - 输出是数据流到相应的
SortExec节点。
- 每个
SortExec节点并行地按city(升序)和time(降序)对数据进行排序。排序是SortPreservingMergeExec计划所需的。UnionExec节点连接流以合并并行SortExec节点的输出。- 此
SortPreservingMergeExec节点合并之前排序和并集的数据来自UnionExec。
空结果集的EXPLAIN报告示例
如果你的表在查询的时间范围内没有数据,物理计划将以一个 EmptyExec 叶节点开始–例如:
ProjectionExec: expr=[temp@0 as temp]
SortExec: expr=[time@1 ASC NULLS LAST]
EmptyExec: produce_one_row=false
分析前沿数据的查询计划
以下章节将指导您分析一个典型时间序列用例的物理查询计划——聚合最近写入的 (领先边缘) 数据。尽管查询和计划比在 前面的示例 中更复杂,但您将遵循相同的 读取查询计划的步骤。在学习如何读取查询计划后,您将理解 ExecutionPlans、数据流以及潜在的查询瓶颈。
示例数据
考虑以下 h20 数据,表示为写入 InfluxDB 的“块”行协议:
// h20 data
// The following data represents 5 batches, or "chunks", of line protocol
// written to InfluxDB.
// - Chunks 1-4 are ingested and each is persisted to a separate partition file in storage.
// - Chunk 5 is ingested and not yet persisted to storage.
// - Chunks 1 and 2 cover short windows of time that don't overlap times in other chunks.
// - Chunks 3 and 4 cover larger windows of time and the time ranges overlap each other.
// - Chunk 5 contains the largest time range and overlaps with chunk 4, the Parquet file with the largest time-range.
// - In InfluxDB, a chunk never duplicates its own data.
//
// Chunk 1: stored Parquet file
// - time range: 50-249
// - no duplicates in its own chunk
// - no overlap with any other chunks
[
"h2o,state=MA,city=Bedford min_temp=71.59 150",
"h2o,state=MA,city=Boston min_temp=70.4, 50",
"h2o,state=MA,city=Andover max_temp=69.2, 249",
],
// Chunk 2: stored Parquet file
// - time range: 250-349
// - no duplicates in its own chunk
// - no overlap with any other chunks
// - adds a new field (area)
[
"h2o,state=CA,city=SF min_temp=79.0,max_temp=87.2,area=500u 300",
"h2o,state=CA,city=SJ min_temp=75.5,max_temp=84.08 349",
"h2o,state=MA,city=Bedford max_temp=78.75,area=742u 300",
"h2o,state=MA,city=Boston min_temp=65.4 250",
],
// Chunk 3: stored Parquet file
// - time range: 350-500
// - no duplicates in its own chunk
// - overlaps chunk 4
[
"h2o,state=CA,city=SJ min_temp=77.0,max_temp=90.7 450",
"h2o,state=CA,city=SJ min_temp=69.5,max_temp=88.2 500",
"h2o,state=MA,city=Boston min_temp=68.4 350",
],
// Chunk 4: stored Parquet file
// - time range: 400-600
// - no duplicates in its own chunk
// - overlaps chunk 3
[
"h2o,state=CA,city=SF min_temp=68.4,max_temp=85.7,area=500u 600",
"h2o,state=CA,city=SJ min_temp=69.5,max_temp=89.2 600", // duplicates row 3 in chunk 5
"h2o,state=MA,city=Bedford max_temp=80.75,area=742u 400", // overlaps chunk 3
"h2o,state=MA,city=Boston min_temp=65.40,max_temp=82.67 400", // overlaps chunk 3
],
// Chunk 5: Ingester data
// - time range: 550-700
// - overlaps and duplicates data in chunk 4
[
"h2o,state=MA,city=Bedford max_temp=88.75,area=742u 600", // overlaps chunk 4
"h2o,state=CA,city=SF min_temp=68.4,max_temp=85.7,area=500u 650",
"h2o,state=CA,city=SJ min_temp=68.5,max_temp=90.0 600", // duplicates row 2 in chunk 4
"h2o,state=CA,city=SJ min_temp=75.5,max_temp=84.08 700",
"h2o,state=MA,city=Boston min_temp=67.4 550", // overlaps chunk 4
]
以下查询选择所有数据:
SELECT state, city, min_temp, max_temp, area, time
FROM h2o
ORDER BY state asc, city asc, time desc;
输出如下:
+-------+---------+----------+----------+------+--------------------------------+
| state | city | min_temp | max_temp | area | time |
+-------+---------+----------+----------+------+--------------------------------+
| CA | SF | 68.4 | 85.7 | 500 | 1970-01-01T00:00:00.000000650Z |
| CA | SF | 68.4 | 85.7 | 500 | 1970-01-01T00:00:00.000000600Z |
| CA | SF | 79.0 | 87.2 | 500 | 1970-01-01T00:00:00.000000300Z |
| CA | SJ | 75.5 | 84.08 | | 1970-01-01T00:00:00.000000700Z |
| CA | SJ | 68.5 | 90.0 | | 1970-01-01T00:00:00.000000600Z |
| CA | SJ | 69.5 | 88.2 | | 1970-01-01T00:00:00.000000500Z |
| CA | SJ | 77.0 | 90.7 | | 1970-01-01T00:00:00.000000450Z |
| CA | SJ | 75.5 | 84.08 | | 1970-01-01T00:00:00.000000349Z |
| MA | Andover | | 69.2 | | 1970-01-01T00:00:00.000000249Z |
| MA | Bedford | | 88.75 | 742 | 1970-01-01T00:00:00.000000600Z |
| MA | Bedford | | 80.75 | 742 | 1970-01-01T00:00:00.000000400Z |
| MA | Bedford | | 78.75 | 742 | 1970-01-01T00:00:00.000000300Z |
| MA | Bedford | 71.59 | | | 1970-01-01T00:00:00.000000150Z |
| MA | Boston | 67.4 | | | 1970-01-01T00:00:00.000000550Z |
| MA | Boston | 65.4 | 82.67 | | 1970-01-01T00:00:00.000000400Z |
| MA | Boston | 68.4 | | | 1970-01-01T00:00:00.000000350Z |
| MA | Boston | 65.4 | | | 1970-01-01T00:00:00.000000250Z |
| MA | Boston | 70.4 | | | 1970-01-01T00:00:00.000000050Z |
+-------+---------+----------+----------+------+--------------------------------+
示例查询
以下查询从样本数据中选择领先边缘数据:
SELECT city, count(1)
FROM h2o
WHERE time >= to_timestamp(200) AND time < to_timestamp(700)
AND state = 'MA'
GROUP BY city
ORDER BY city ASC;
输出如下:
+---------+-----------------+
| city | COUNT(Int64(1)) |
+---------+-----------------+
| Andover | 1 |
| Bedford | 3 |
| Boston | 4 |
+---------+-----------------+
领先边缘数据查询的EXPLAIN报告
以下查询生成前面的 EXPLAIN 报告 示例查询:
EXPLAIN SELECT city, count(1)
FROM h2o
WHERE time >= to_timestamp(200) AND time < to_timestamp(700)
AND state = 'MA'
GROUP BY city
ORDER BY city ASC;
在示例数据中,注释告诉您哪些数据块重叠或在其他数据块中重复。 如果在两个数据块中存在相同时间段的数据,则这两个数据块重叠。 您将在本指南的后面部分学习如何识别重叠和重复的数据。
与示例数据不同,您的数据可能不会告诉您重叠或重复的地方。 物理计划可以揭示您数据中的重叠和重复以及它们如何影响您的查询——例如,在学习如何读取物理计划后,您可能会将数据扫描步骤总结如下:
- 查询执行开始时有两个
ParquetExec和一个RecordBatchesExec执行计划,它们并行运行。 - 第一个
ParquetExec节点读取两个不重叠其他文件并且不重复数据的文件;这些文件不需要去重。 - 第二个
ParquetExec节点读取两个相互重叠的文件,并重叠在RecordBatchesExec节点中扫描的已摄取数据;查询计划必须在完成查询之前包含这些节点的去重过程。
剩余部分分析示例物理计划中的 ExecutionPlan 节点结构和参数。 示例包括特定于 DataFusion 和 InfluxDB 的 ExecutionPlan 节点。
定位物理计划
要开始分析查询的物理计划,请找到EXPLAIN 报告中 plan_type 列值为 physical_plan 的行。该行的 plan 列包含物理计划。
读取物理计划
以下部分遵循步骤来 读取查询计划 并检查物理计划节点及其输入和输出。
要 读取查询计划的执行流程,始终从最内层(叶子)节点开始,向上读取到最外层的根节点。
物理计划叶节点

物理计划中的叶子节点结构
数据扫描节点 (ParquetExec 和 RecordBatchesExec)
这个 示例物理计划 包含三个 叶子节点——执行流程开始的最内层节点:
ParquetExec节点从 对象存储 中检索和扫描 Parquet 文件中的数据- a
RecordBatchesExec节点从 Ingester 中获取最近写入的、尚未持久化的数据
因为 ParquetExec 和 RecordBatchesExec 为查询检索和扫描数据,每个查询计划都以一个或多个这些节点开始。
ParquetExec 和 RecordBatchesExec 节点的数量及其参数值可以告诉您您的查询检索了哪些数据(以及多少数据),以及计划如何高效处理数据的组织(例如,分区和去重)。
为方便起见,本指南使用名称 ParquetExec_A 和 ParquetExec_B 代表 ParquetExec 节点,在示例物理计划中。从物理计划的顶部开始, ParquetExec_A 是物理计划中的第一个叶子节点,而 ParquetExec_B 是最后一个(底部)叶子节点。
这些名称表示节点在报告中的位置,而不是它们的执行顺序。
ParquetExec_A
ParquetExec: file_groups={2 groups: [[1/1/b862a7e9b329ee6a418cde191198eaeb1512753f19b87a81def2ae6c3d0ed237/243db601-f3f1-401b-afda-82160d8cc1a8.Parquet], [1/1/b862a7e9b329ee6a418cde191198eaeb1512753f19b87a81def2ae6c3d0ed237/f5fb7c7d-16ac-49ba-a811-69578d05843f.Parquet]]}, projection=[city, state, time], output_ordering=[state@1 ASC, city@0 ASC, time@2 ASC], predicate=time@5 >= 200 AND time@5 < 700 AND state@4 = MA, pruning_predicate=time_max@0 >= 200 AND time_min@1 < 700 AND state_min@2 <= MA AND MA <= state_max@3 |
ParquetExec_A,第一个 ParquetExec 节点
ParquetExec_A具有以下特征:
file_groups
一个 文件组 是供操作员读取的文件列表。文件通过路径引用:
1/1/b862a7e9b.../243db601-....parquet1/1/b862a7e9b.../f5fb7c7d-....parquet
路径结构表示您的数据是如何组织的。 您可以使用文件路径获取有关查询的更多信息,例如:
- 在目录中查找文件信息(例如:大小和行数)
- 从对象存储下载Parquet文件以进行调试
- 查找查询读取了多少个分区
路径具有以下结构:
<namespace_id>/<table_id>/<partition_hash_id>/<uuid_of_the_file>.Parquet
1 / 1 /b862a7e9b329ee6a4.../243db601-f3f1-4....Parquet
namespace_id: 正在查询的命名空间(数据库)table_id: 正在查询的表(测量)partition_hash_id: 该文件所属的分区。 你可以计算分区 ID 以找出查询读取了多少个分区。uuid_of_the_file: 文件标识符。
ParquetExec 并行处理组,并顺序读取每个组中的文件。
file_groups={2 groups: [[1/1/b862a7e9b329ee6a4/243db601....parquet], [1/1/b862a7e9b329ee6a4/f5fb7c7d....parquet]]}
{2 groups: [[file], [file]}: ParquetExec_A 接收两个组,每个组有一个文件。
因此,ParquetExec_A 并行读取两个文件。
projection
projection 列出 ExecutionPlan 要读取和输出的表列。
projection=[city, state, time]
output_ordering
output_ordering 指定 ExecutionPlan 输出的排序顺序。
查询规划器会在输出需要排序并且规划器知道顺序时传递该参数。
output_ordering=[state@2 ASC, city@1 ASC, time@3 ASC, __chunk_order@0 ASC]
在将数据存储到Parquet文件时,InfluxDB对数据进行排序,以提高存储压缩和查询效率,计划程序尽可能长时间地保持该顺序。一般来说,output_ordering值是ParquetExec接收的存储数据的排序(或排序的子集)。
根据设计, RecordBatchesExec 数据是未排序的。
在这个例子中,计划者指定 ParquetExec_A 使用现有的排序顺序 state ASC, city ASC, time ASC, 作为输出。
要查看您存储数据的排序顺序,请为 SELECT ALL 查询生成一个 EXPLAIN 报告,例如:
EXPLAIN SELECT * FROM TABLE_NAME WHERE time > now() - interval '1 hour'
如果查询返回的数据过多,请缩小时间范围。
predicate
predicate 是查询中指定的数据过滤器。
predicate=time@5 >= 200 AND time@5 < 700 AND state@4 = MA
pruning predicate
pruning_predicate 是由 predicate 值创建的,是实际用于从所选分区修剪数据和文件的谓词。默认情况下按 time 过滤文件。
pruning_predicate=time_max@0 >= 200 AND time_min@1 < 700 AND state_min@2 <= MA AND MA <= state_max@3
在物理计划生成之前,额外的 partition pruning 步骤使用分区列上的谓词来修剪分区。
RecordBatchesExec
RecordBatchesExec: chunks=1, projection=[__chunk_order, city, state, time]
RecordBatchesExec 是一个 InfluxDB 特定的 ExecutionPlan 实现,它从 Ingester 获取最近写入但尚未持久化的数据。
在这个例子中, RecordBatchesExec 包含以下表达式:
chunks
chunks 是从 Ingester 接收到的数据块的数量。
chunks=1
chunks=1:RecordBatchesExec接收一个数据块。
projection
投影列表指定了节点要读取和输出的列或表达式。
[__chunk_order, city, state, time]
__chunk_order: 对数据块和文件进行去重排序city, state, time: 在ParquetExec_A projection中指定的相同列
在数据扫描节点中出现 __chunk_order 表明数据在节点之间存在重叠,并且可能是重复的。
ParquetExec_B
在示例物理计划中,最底部的叶子节点是另一个ParquetExec操作符,ParquetExec_B。
ParquetExec_B 表达式
ParquetExec:
file_groups={2 groups: [[1/1/b862a7e9b.../2cbb3992-....Parquet],
[1/1/b862a7e9b.../9255eb7f-....Parquet]]},
projection=[__chunk_order, city, state, time],
output_ordering=[state@2 ASC, city@1 ASC, time@3 ASC, __chunk_order@0 ASC],
predicate=time@5 >= 200 AND time@5 < 700 AND state@4 = MA,
pruning_predicate=time_max@0 >= 200 AND time_min@1 < 700 AND state_min@2 <= MA AND MA <= state_max@3
因为 ParquetExec_B 存在重叠,projection 和 output_ordering 表达式使用了在 RecordBatchesExec projection 中使用的 __chunk_order 列。
在数据扫描节点中出现 __chunk_order 表明数据在节点之间存在重叠,并且可能是重复的。
剩余的 ParquetExec_B 表达式与 ParquetExec_A 中的类似。
查询计划如何分配数据以进行扫描
如果你比较 file_group 路径在 ParquetExec_A 和 ParquetExec_B 中,你会注意到两者都包含来自同一分区的文件:
1/1/b862a7e9b329ee6a4.../...
规划器可能出于几个原因将来自同一分区的文件分发到不同的扫描节点,包括处理重叠的优化——例如:
- 将不重叠的文件与重叠的文件分开,以最小化去重所需的工作量(在此示例中就是这种情况)
- 分发不重叠的文件以增加并行执行
分析分支结构
在数据从数据扫描节点输出后,它流向下一个父(外部)节点。
在示例计划中:
- 每个叶节点是计划处理扫描数据的一系列节点中的第一步。
- 三个分支并行执行。
- 在叶节点之后,每个分支包含以下类似的节点结构:
...
CoalesceBatchesExec: target_batch_size=8192
FilterExec: time@3 >= 200 AND time@3 < 700 AND state@2 = MA
...
FilterExec: time@3 >= 200 AND time@3 < 700 AND state@2 = MA: 根据条件time@3 >= 200 AND time@3 < 700 AND state@2 = MA过滤数据,并确保所有数据都被修剪。CoalesceBatchesExec: target_batch_size=8192: 将小批次合并为更大批次。请参阅DataFusion [CoalesceBatchesExec] 文档。
排序尚未持久化的数据
在 RecordBatchesExec 分支中,紧跟在 CoalesceBatchesExec 之后的是一个 SortExec 节点:
SortExec: expr=[state@2 ASC,city@1 ASC,time@3 ASC,__chunk_order@0 ASC]
该节点使用指定的表达式 state ASC, city ASC, time ASC, __chunk_order ASC 对尚未持久化的数据进行排序。
既没有 ParquetExec_A 也没有 ParquetExec_B 包含类似的节点,因为对象存储中的数据已经按照给定顺序排序(由 Ingester 或 Compactor);查询计划只需要对从 Ingester 到达的数据进行排序。
识别重叠和重复的数据
在示例物理计划中,ParquetExec_B 和 RecordBatchesExec 节点共享以下父节点:
...
DeduplicateExec: [state@2 ASC,city@1 ASC,time@3 ASC]
SortPreservingMergeExec: [state@2 ASC,city@1 ASC,time@3 ASC,__chunk_order@0 ASC]
UnionExec
...
UnionExec: 通过连接分区来合并多个输入数据流。UnionExec不进行任何合并,执行速度快。SortPreservingMergeExec: [state@2 ASC,city@1 ASC,time@3 ASC,__chunk_order@0 ASC]: 合并已经排序的数据;表示前面的数据(来自下面的节点)已经排序。输出数据是单一的排序流。DeduplicateExec: [state@2 ASC,city@1 ASC,time@3 ASC]: 去重已排序数据的输入流。 因为SortPreservingMergeExec确保了单个排序流,它通常,但并非总是, precedesDeduplicateExec。
A DeduplicateExec 节点表示包含的节点有 重叠数据–一个文件或批次中的数据与另一个文件或批次中的数据具有相同的时间戳范围。由于 InfluxDB 组织数据的方式,数据在一个文件 内部 从不重复。
在这个示例中,DeduplicateExec 节点包含 ParquetExec_B 和 RecordBatchesExec 节点,这表明 ParquetExec_B 文件组 文件与尚未持久化的数据重叠。
以下样本数据摘录显示了文件与Ingester数据之间的重叠数据:
// Chunk 4: stored Parquet file
// - time range: 400-600
[
"h2o,state=CA,city=SF min_temp=68.4,max_temp=85.7,area=500u 600",
],
// Chunk 5: Ingester data
// - time range: 550-700
// - overlaps and duplicates data in chunk 4
[
"h2o,state=MA,city=Bedford max_temp=88.75,area=742u 600", // overlaps chunk 4
...
"h2o,state=MA,city=Boston min_temp=67.4 550", // overlaps chunk 4
]
如果文件或摄取的数据重叠,查询者必须在查询计划中包含 DeduplicateExec 以去除任何重复项。
DeduplicateExec 并不一定表示数据是重复的。
如果一个计划读取了多个文件并对所有文件执行去重,可能是出于以下原因:
- 文件包含重复数据
- 对象存储有许多小的重叠文件,压缩器还没有对其进行压缩。压缩后,您的查询可能表现得更好,因为需要读取的文件更少
- 压实机跟不上了
一个叶节点,如果在其分支中没有 DeduplicateExec 节点,则不需要去重,并且不会与其他文件或 Ingester 数据重叠——例如,ParquetExec_A 没有重叠:
ProjectionExec:...
CoalesceBatchesExec:...
FilterExec:...
ParquetExec:...
缺少DeduplicateExec节点意味着文件没有重叠。
数据扫描输出
ProjectionExec 节点过滤列,以便输出中仅保留 city 列:
`ProjectionExec: expr=[city@0 as city]`
最终处理
在每个叶子节点中去重和过滤数据后,该计划组合输出,然后应用聚合和排序操作以获得最终结果:
| physical_plan | SortPreservingMergeExec: [city@0 ASC NULLS LAST] |
| | SortExec: expr=[city@0 ASC NULLS LAST] |
| | AggregateExec: mode=FinalPartitioned, gby=[city@0 as city], aggr=[COUNT(Int64(1))] |
| | CoalesceBatchesExec: target_batch_size=8192 |
| | RepartitionExec: partitioning=Hash([city@0], 4), input_partitions=4 |
| | AggregateExec: mode=Partial, gby=[city@0 as city], aggr=[COUNT(Int64(1))] |
| | RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=3 |
| | UnionExec
用于聚合、排序和最终输出的操作符结构。
UnionExec: 联合数据流。请注意,输出流的数量与输入流的数量相同——UnionExec节点是向下游操作符的中间步骤,这些操作符实际上合并或分割数据流。RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=3: 将三个输入流均匀拆分为四个输出流。该计划拆分流以增加并行执行。AggregateExec: mode=Partial, gby=[city@0 as city], aggr=[COUNT(Int64(1))]: 根据查询中的规定对数据进行分组:city, count(1)。 该节点分别对四个流进行聚合,然后输出四个流,标识为mode=Partial–数据尚未完全聚合。RepartitionExec: partitioning=Hash([city@0], 4), input_partitions=4: 在Hash([city])上重新分区数据,并分成四个流——每个流包含一个城市的数据。AggregateExec: mode=FinalPartitioned, gby=[city@0 as city], aggr=[COUNT(Int64(1))]: 将最终聚合(aggr=[COUNT(Int64(1))])应用于数据。mode=FinalPartitioned表示数据已经按(城市)分区,不需要进一步通过AggregateExec进行分组。SortExec: expr=[city@0 ASC NULLS LAST]: 按照查询中指定的city对四个数据流进行排序。SortPreservingMergeExec: [city@0 ASC NULLS LAST]: 合并并排序四个已排序的流以生成最终输出。
在前面的示例中,EXPLAIN 报告显示了查询计划,而没有执行查询。要查看运行时指标,例如计划及其操作符的执行时间,请使用 EXPLAIN ANALYZE 生成报告,并在必要时使用 tracing 进行进一步调试。