创建和解释查询计划
您可以借助查询计划获取有关运行查询所需的各个操作的信息。在处理查询计划前,建议您先了解 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 系统表。
-
空间联接
通常是基于空间数据邻近度的快速联接,用于
GEOMETRY
和GEOGRAPHY
数据类型。
聚合运算符
查询计划在涉及聚合函数和 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
两个表都重新分配。