创建和解释查询计划 - Amazon Redshift

创建和解释查询计划

您可以借助查询计划获取有关运行查询所需的各个操作的信息。在处理查询计划前,建议您先了解 Amazon Redshift 如何处理查询和如何创建查询计划。有关更多信息,请参阅 查询计划和执行工作流程

要创建查询计划,请运行 EXPLAIN 命令,后跟实际查询文本。查询计划提供以下信息:

  • 执行引擎将执行的操作,自下而上地阅读结果。

  • 每个操作执行的步骤的类型。

  • 每个操作中使用的表和列。

  • 每个操作中处理的数据量(以字节为单位),以行数和数据宽度计。

  • 操作的相对成本。成本是比较计划内的步骤的相对执行时间的度量。成本不提供有关实际执行时间或内存消耗的任何精确信息,也没有提供执行计划之间的有意义的比较。它可以指示查询中的哪些操作消耗最多的资源。

EXPLAIN 命令不实际运行查询。它只显示当查询在当前操作条件下运行时 Amazon Redshift 将执行的计划。如果您更改表的 schema 或数据后再次运行 ANALYZE 以更新统计元数据,则查询计划可能会不同。

EXPLANE 的查询计划输出是查询执行的简化高级视图。它不描述并行查询处理的详细信息。要查看详细信息,请运行查询本身,然后从 SVL_QUERY_SUMMARY 或 SVL_QUERY_REPORT 视图获取查询摘要信息。有关使用这些视图的更多信息,请参阅分析查询摘要

以下示例显示 EVENT 表上的简单 GROUP BY 查询的 EXPLAIN 输出:

explain select eventname, count(*) from event group by eventname; QUERY PLAN ------------------------------------------------------------------- XN HashAggregate (cost=131.97..133.41 rows=576 width=17) -> XN Seq Scan on event (cost=0.00..87.98 rows=8798 width=17)

EXPLAIN 为每个操作返回以下指标:

费用

对于比较计划内的操作非常有用的相对值。成本由被两个圆点分隔的十进制值组成,例如 cost=131.97..133.41。第一个值(在本例中为 131.97)提供返回此操作的第一行的相对成本。第二个值(在本例中为 133.41)提供完成操作的相对成本。查询计划的成本在读取计划时进行累积,因此本示例中的 HashAggregate 成本 (131.97..133.41) 包括其下面的序列扫描的成本 (0.00..87.98)。

行数

要返回的估计行数。在此示例中,扫描预计将返回 8798 行。HashAggregate 运算符本身应返回 576 行(在从结果集中丢弃重复的事件名称之后)。

注意

行数估算基于 ANALYZE 命令生成的可用统计数据。如果最近未运行过 ANALYZE,则估算的可靠性会降低。

宽度

平均行的估计宽度(以字节为单位)。在此示例中,平均行的宽度应为 17 个字节。

EXPLAIN 运算符

本节简要介绍了在 EXPLAIN 输出中最常见的运算符。有关运算符的完整列表,请参阅 SQL 命令部分中的EXPLAIN

顺序扫描运算符

顺序扫描运算符 (Seq Scan) 指示表扫描。Seq Scan 扫描从开始到结束按顺序扫描表中的每一列,并计算每一行的查询约束(在 WHERE 子句中)。

联接运算符

Amazon Redshift 根据要联接的表的物理设计、联接所需的数据的位置以及查询本身的特定要求来选择联接运算符。

  • 嵌套循环

    最优化程度最差的联接,即嵌套循环,主要用于交叉联接(笛卡尔积)和一些不等式联接。

  • 哈希联接和哈希

    哈希联接和哈希的运行速度通常比签到循环快,可用于内部联接以及左和右外部联接。这些运算符在联接列不是分配键排序键的情况下用于联接表。哈希运算符为联接中的内部表创建哈希表;哈希联接运算符读取外部表,对联接列进行哈希处理,然后在内部哈希表中查找匹配项。

  • 合并联接

    合并联接通常是最快的连接,用于内联接和外联接。合并联接不用于完全联接。此运算符在联接列都是分配键排序键的情况下,以及未排序的联接表少于 20% 时用于联接表。它按顺序读取两个排序表并查找匹配的行。要查看未排序行的百分比,请查询 SVV_TABLE_INFO 系统表。

  • 空间联接

    通常是基于空间数据邻近度的快速联接,用于 GEOMETRYGEOGRAPHY 数据类型。

聚合运算符

查询计划在涉及聚合函数和 GROUP BY 操作的查询中使用以下运算符。

  • 聚合

    标量聚合函数(如 AVG 和 SUM)的运算符。

  • HashAggregate

    未排序分组聚合函数的运算符。

  • GroupAggregate

    已排序分组聚合函数的运算符。

排序运算符

当查询必须对结果集进行排序或合并时,查询计划使用以下运算符。

  • 排序

    评估 ORDER BY 子句和其他排序操作,例如 UNION 查询和联接所需的排序、SELECT DISTINCT 查询和窗口函数。

  • 合并

    根据从并行操作得到的临时排序结果,来生成最终排序结果。

UNION、INTERSECT 和 EXCEPT 运算符

查询计划将以下运算符用于涉及使用 UNION、INTERSECT 和 EXCEPT 进行集合操作的查询。

  • Subquery

    用于运行 UNION 查询。

  • Hash Intersect Distinct

    用于运行 INTERSECT 查询。

  • SetOp Except

    用于运行 EXCEPT(或 MINUS)查询。

其他运算符

以下运算符也经常出现在例行查询的 EXPLAIN 输出中。

  • 唯一

    消除 SELECT DISTINCT 查询和 UNION 查询的重复项。

  • 限制

    处理 LIMIT 子句。

  • 窗口

    运行窗口函数。

  • 结果

    运行不涉及任何表访问的标量函数。

  • 子计划

    用于特定的子查询。

  • Network

    将临时结果发送到领导节点,以待进一步处理。

  • 实体化

    保存嵌套循环联接和某些合并联接的输入中的行。

EXPLAIN 中的联接

查询优化程序使用不同的联接类型来检索表数据,具体取决于查询和基础表的结构。EXPLAIN 输出引用了联接类型、使用的表以及在集群中分布表数据的方式,以描述查询的处理方式。

联接类型示例

下面的示例显示了查询优化程序可以使用的不同联接类型。查询计划中使用的联接类型取决于所涉表的物理设计。

示例:对两个表进行哈希联接

以下查询在 CATID 列上将 EVENT 和 CATEGORY 联接起来。CATID 是 CATEGORY 的分配和排序键,但不适用于 EVENT。使用 EVENT 作为外部表并使用 CATEGORY 作为内部表来执行哈希联接。由于 CATEGORY 是较小的表,因此计划程序在查询处理过程中使用 DS_BCAST_INNER 将其副本广播到计算节点。此示例中的联接成本占计划累计成本的大部分。

explain select * from category, event where category.catid=event.catid; QUERY PLAN ------------------------------------------------------------------------- XN Hash Join DS_BCAST_INNER (cost=0.14..6600286.07 rows=8798 width=84) Hash Cond: ("outer".catid = "inner".catid) -> XN Seq Scan on event (cost=0.00..87.98 rows=8798 width=35) -> XN Hash (cost=0.11..0.11 rows=11 width=49) -> XN Seq Scan on category (cost=0.00..0.11 rows=11 width=49)
注意

EXPLAIN 输出中运算符的缩进对齐有时表示这些操作不相互依赖,并且可以并行开始。在前面的示例中,尽管 EVENT 表上的扫描和哈希操作已对齐,但 EVENT 扫描必须等到哈希操作完全完成。

示例:对两个表进行合并联接

以下查询还使用 SELECT *,但它在 LISTID 列上联接 SALES 和 LISTING,其中 LISTID 已设置为两个表的分配和排序键。选择合并联接,并且不需要对联接重新分配数据 (DS_DIST_NONE)。

explain select * from sales, listing where sales.listid = listing.listid; QUERY PLAN ----------------------------------------------------------------------------- XN Merge Join DS_DIST_NONE (cost=0.00..6285.93 rows=172456 width=97) Merge Cond: ("outer".listid = "inner".listid) -> XN Seq Scan on listing (cost=0.00..1924.97 rows=192497 width=44) -> XN Seq Scan on sales (cost=0.00..1724.56 rows=172456 width=53)

以下示例演示了同一查询中的不同类型的联接。与前面的示例一样,SALES 和 LISTING 是合并联接的,但第三个表 EVENT 必须与合并联接的结果进行哈希联接。同样,哈希联接会产生广播成本。

explain select * from sales, listing, event where sales.listid = listing.listid and sales.eventid = event.eventid; QUERY PLAN ---------------------------------------------------------------------------- XN Hash Join DS_BCAST_INNER (cost=109.98..3871130276.17 rows=172456 width=132) Hash Cond: ("outer".eventid = "inner".eventid) -> XN Merge Join DS_DIST_NONE (cost=0.00..6285.93 rows=172456 width=97) Merge Cond: ("outer".listid = "inner".listid) -> XN Seq Scan on listing (cost=0.00..1924.97 rows=192497 width=44) -> XN Seq Scan on sales (cost=0.00..1724.56 rows=172456 width=53) -> XN Hash (cost=87.98..87.98 rows=8798 width=35) -> XN Seq Scan on event (cost=0.00..87.98 rows=8798 width=35)

示例:联接、聚合和排序

以下查询运行 SALES 和 EVENT 表的哈希联接,然后执行聚合和排序操作,以考虑分组 SUM 函数和 ORDER BY 子句。初始排序运算符在计算节点上并行运行。然后,Network 运算符将结果发送到领导节点,其中合并运算符生成最终的排序结果。

explain select eventname, sum(pricepaid) from sales, event where sales.eventid=event.eventid group by eventname order by 2 desc; QUERY PLAN --------------------------------------------------------------------------------- XN Merge (cost=1002815366604.92..1002815366606.36 rows=576 width=27) Merge Key: sum(sales.pricepaid) -> XN Network (cost=1002815366604.92..1002815366606.36 rows=576 width=27) Send to leader -> XN Sort (cost=1002815366604.92..1002815366606.36 rows=576 width=27) Sort Key: sum(sales.pricepaid) -> XN HashAggregate (cost=2815366577.07..2815366578.51 rows=576 width=27) -> XN Hash Join DS_BCAST_INNER (cost=109.98..2815365714.80 rows=172456 width=27) Hash Cond: ("outer".eventid = "inner".eventid) -> XN Seq Scan on sales (cost=0.00..1724.56 rows=172456 width=14) -> XN Hash (cost=87.98..87.98 rows=8798 width=21) -> XN Seq Scan on event (cost=0.00..87.98 rows=8798 width=21)

数据重新分配

联接的 EXPLAIN 输出还指定在集群上移动数据以便进行联接的方法。数据移动可以采用广播方法或重新分配。在广播中,联接一侧的数据值将从每个计算节点复制到每个其他计算节点,以便每个计算节点最终得到数据的完整副本。在重新分配中,参与的数据值从其当前切片发送到新切片(可能位于不同节点上)。如果该分配键是联接列之一,则通常会重新分配数据以匹配参与联接的其他表的分配键。如果两个表在其中一个联接列上都没有分配键,则两个表都会被分配,或者内部表将广播到每个节点。

EXPLAIN 输出还引用内表和外表。首先扫描内部表,并显示在查询计划底部附近。内部表是用来探测匹配项的表。它通常保存在内存中,通常是哈希的源表,如果可能的话,是两者中较小的表。外部表是要与内部表匹配的行的源。它通常是从磁盘读取的。查询优化程序根据最新运行的 ANALYZE 命令中的数据库统计信息选择内表和外表。查询的 FROM 子句中的表顺序并不区分内部表和外部表。

您可以通过查询计划中的以下属性了解数据的移动方式,以便执行查询:

  • DS_BCAST_INNER

    将整个内部表的副本广播到所有计算节点。

  • DS_DIST_ALL_NONE

    无需重新分配,因为内部表已经使用 DISTSTYLE ALL 被分配到每个节点。

  • DS_DIST_NONE

    两个表都未重新分配。可以使用并置连接,因为相应的切片联接时不会在节点之间移动数据。

  • DS_DIST_INNER

    内部表重新分配。

  • DS_DIST_OUTER

    外部表重新分配。

  • DS_DIST_ALL_INNER

    整个内部表重新分配到单个切片,因为外部表使用 DISTSTYLE ALL。

  • DS_DIST_BOTH

    两个表都重新分配。