

 从补丁 198 开始，Amazon Redshift 将不再支持创建新的 Python UDF。现有的 Python UDF 将继续正常运行至 2026 年 6 月 30 日。有关更多信息，请参阅[博客文章](https://aws.amazon.com/blogs/big-data/amazon-redshift-python-user-defined-functions-will-reach-end-of-support-after-june-30-2026/)。

# 查询处理
<a name="c-query-processing"></a>

Amazon Redshift 通过分析程序和优化程序路由提交的 SQL 查询，以制订查询计划。然后，执行引擎将查询计划转换为代码并将代码发送到计算节点执行。

**Topics**
+ [查询计划和执行工作流程](c-query-planning.md)
+ [创建和解释查询计划](c-the-query-plan.md)
+ [检查查询计划步骤](reviewing-query-plan-steps.md)
+ [影响查询性能的因素](c-query-performance.md)

# 查询计划和执行工作流程
<a name="c-query-planning"></a>

下图概要介绍查询计划和执行工作流程。

![\[领导节点的查询计划和执行工作流。\]](http://docs.aws.amazon.com/zh_cn/redshift/latest/dg/images/07-QueryPlanning.png)


查询计划和执行工作流程遵循以下步骤：

1. 领导节点接收查询并解析 SQL。

1. 分析程序生成初步查询树，后者是原始查询的逻辑表示。然后，Amazon Redshift 将该查询树输入到查询优化程序中。

1. 优化程序评估并在必要时重写查询以最大限度地提高查询的效率。此过程有时会导致创建多个相关查询来替换单个查询。

1. 优化程序会生成一个查询计划以最佳性能进行执行（如果上一步导致多个查询，则生成多个查询计划）。查询计划指定执行选项，如联接类型、联接顺序、聚合选项和数据分配要求。

   您可以使用 [EXPLAIN](r_EXPLAIN.md) 命令查看查询计划。查询计划是分析和优化复杂查询的基本工具。有关更多信息，请参阅 [创建和解释查询计划](c-the-query-plan.md)。

1. 执行引擎将查询计划转换为*步骤*、*分段*和*流*：  
**步骤**  
每个步骤都是查询执行过程中需要的单独操作。可以组合步骤以允许计算节点执行查询、联接或其他数据库操作。  
**Segment**  
可以通过单个进程完成的几个步骤的组合，也是由计算节点切片执行的最小编译单元。*切片*是 Amazon Redshift 中并行处理的单元。流中的分段并行运行。  
**流**  
要分配到可用计算节点切片上的分段的集合。

   执行引擎基于步骤、段和流生成编译后的代码。编译的代码运行速度比解释的代码快，并且使用的计算容量更少。然后，将此编译的代码广播到计算节点。
**注意**  
在对查询进行基准测试时，您应始终比较查询第二次执行的时间，因为第一次执行时间包括编译代码的开销。有关更多信息，请参阅 [影响查询性能的因素](c-query-performance.md)。

1. 计算节点切片并行运行查询分段。在该流程中，Amazon Redshift 利用优化的网络通信、内存和磁盘管理，将中间结果从一个查询计划步骤传递到下一个。这还有助于加快查询执行速度。

步骤 5 和 6 针对每个流执行一次。引擎为一个流创建可执行分段并将其发送到计算节点。当该流的分段完成时，引擎会生成下一个流的分段。通过这种方式，引擎可以分析先前流中发生的情况（例如，操作是否基于磁盘），以影响下一个流中分段的生成。

计算节点完成后，它们会将查询结果返回到领导节点以进行最终处理。领导节点将数据合并到单个结果集中，并解决任何需要的排序或聚合。然后，领导节点将结果返回至客户端。

**注意**  
如有必要，计算节点可能会在查询执行期间将某些数据返回到领导节点。例如，如果您有一个包含 LIMIT 子句的子查询，则在数据在集群间重新分配以进一步处理前会在领导节点上应用该限制。

# 创建和解释查询计划
<a name="c-the-query-plan"></a>

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

要创建查询计划，请运行 [EXPLAIN](r_EXPLAIN.md) 命令，后跟实际查询文本。查询计划提供以下信息：
+ 执行引擎将执行的操作，自下而上地阅读结果。
+ 每个操作执行的步骤的类型。
+ 每个操作中使用的表和列。
+ 每个操作中处理的数据量（以字节为单位），以行数和数据宽度计。
+ 操作的相对成本。*成本*是比较计划内的步骤的相对执行时间的度量。成本不提供有关实际执行时间或内存消耗的任何精确信息，也没有提供执行计划之间的有意义的比较。它可以指示查询中的哪些操作消耗最多的资源。

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

EXPLANE 的查询计划输出是查询执行的简化高级视图。它不描述并行查询处理的详细信息。要查看详细信息，请运行查询本身，然后从 SVL\$1QUERY\$1SUMMARY 或 SVL\$1QUERY\$1REPORT 视图获取查询摘要信息。有关使用这些视图的更多信息，请参阅[分析查询摘要](c-analyzing-the-query-summary.md)。

以下示例显示 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 运算符
<a name="EXPLAIN-operators"></a>

本节简要介绍了在 EXPLAIN 输出中最常见的运算符。有关运算符的完整列表，请参阅 SQL 命令部分中的[EXPLAIN](r_EXPLAIN.md)。

### 顺序扫描运算符
<a name="scan-operator"></a>

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

### 联接运算符
<a name="join-operators"></a>

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

  最优化程度最差的联接，即嵌套循环，主要用于交叉联接（笛卡尔积）和一些不等式联接。
+ **哈希联接和哈希**

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

  合并联接通常是最快的连接，用于内联接和外联接。合并联接不用于完全联接。此运算符在联接列都是分配键*和*排序键的情况下，以及未排序的联接表少于 20% 时用于联接表。它按顺序读取两个排序表并查找匹配的行。要查看未排序行的百分比，请查询 [SVV\$1TABLE\$1INFO](r_SVV_TABLE_INFO.md) 系统表。
+ **空间联接**

  通常是基于空间数据邻近度的快速联接，用于 `GEOMETRY` 和 `GEOGRAPHY` 数据类型。

### 聚合运算符
<a name="aggregate-operators"></a>

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

  标量聚合函数（如 AVG 和 SUM）的运算符。
+ **HashAggregate**

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

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

### 排序运算符
<a name="sort-operators"></a>

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

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

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

### UNION、INTERSECT 和 EXCEPT 运算符
<a name="UNION-INTERSECT-and-EXCEPT-operators"></a>

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

  用于运行 UNION 查询。
+ **Hash Intersect Distinct**

  用于运行 INTERSECT 查询。
+ **SetOp Except**

  用于运行 EXCEPT（或 MINUS）查询。

### 其他运算符
<a name="other-operators"></a>

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

  消除 SELECT DISTINCT 查询和 UNION 查询的重复项。
+ **限制**

  处理 LIMIT 子句。
+ **窗口**

  运行窗口函数。
+ **结果**

  运行不涉及任何表访问的标量函数。
+ **子计划**

  用于特定的子查询。
+ **Network**

  将临时结果发送到领导节点，以待进一步处理。
+ **实体化**

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

## EXPLAIN 中的联接
<a name="joins-in-EXPLAIN"></a>

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

### 联接类型示例
<a name="join-types"></a>

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

#### 示例：对两个表进行哈希联接
<a name="hash-join-two-tables"></a>

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

```
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 扫描必须等到哈希操作完全完成。

#### 示例：对两个表进行合并联接
<a name="merge-join-two-tables"></a>

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

```
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)
```

#### 示例：联接、聚合和排序
<a name="join-aggregate-and-sort-example"></a>

以下查询运行 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)
```

### 数据重新分配
<a name="data-redistribution"></a>

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

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

您可以通过查询计划中的以下属性了解数据的移动方式，以便执行查询：
+ **DS\$1BCAST\$1INNER**

  将整个内部表的副本广播到所有计算节点。
+ **DS\$1DIST\$1ALL\$1NONE**

  无需重新分配，因为内部表已经使用 DISTSTYLE ALL 被分配到每个节点。
+ **DS\$1DIST\$1NONE**

  两个表都未重新分配。可以使用并置连接，因为相应的切片联接时不会在节点之间移动数据。
+ **DS\$1DIST\$1INNER**

  内部表重新分配。
+ **DS\$1DIST\$1OUTER**

  外部表重新分配。
+ **DS\$1DIST\$1ALL\$1INNER**

  整个内部表重新分配到单个切片，因为外部表使用 DISTSTYLE ALL。
+ **DS\$1DIST\$1BOTH**

  两个表都重新分配。

# 检查查询计划步骤
<a name="reviewing-query-plan-steps"></a>

您可以通过运行 EXPLAIN 命令来查看查询计划中的步骤。以下示例显示了 SQL 查询并解释了输出。自下而上阅读该查询计划，您可以了解执行该查询所用的每个逻辑操作。有关更多信息，请参阅 [创建和解释查询计划](c-the-query-plan.md)。

```
explain
select eventname, sum(pricepaid) from sales, event
where sales.eventid = event.eventid
group by eventname
order by 2 desc;
```

```
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)
```

作为生成查询计划的一部分，查询优化程序将计划分解为流、分段和步骤。查询优化程序将分解计划，以准备将数据和查询工作负载分配到计算节点。有关流、分段和步骤的更多信息，请参阅[查询计划和执行工作流程](c-query-planning.md)。

下图显示了前面的查询和关联的查询计划。它显示涉及的查询操作如何映射到 Amazon Redshift 用于为计算节点切片生成编译代码的步骤。每个查询计划操作映射到段中的多个步骤，有时映射到流中的多个段。

![\[一个查询及其关联的查询计划映射到三个数据流。\]](http://docs.aws.amazon.com/zh_cn/redshift/latest/dg/images/map-plan-to-streams.png)


在此图中，查询优化程序运行查询计划，如下所示：

1. 在 `Stream 0` 中，查询通过顺序扫描操作运行 `Segment 0`，以扫描 `events` 表。查询继续通过哈希操作运行 `Segment 1`，以为联接中的内部表创建哈希表。

1. 在 `Stream 1` 中，查询通过顺序扫描操作运行 `Segment 2`，以扫描 `sales` 表。它继续使用哈希联接运行 `Segment 2` 以联接表，其中联接列不是分配键和排序键。它再次继续通过哈希聚合来运行 `Segment 2`，以聚合结果。然后，查询使用哈希聚合操作运行 `Segment 3` 来执行未排序的分组聚合函数和排序操作，以评估 ORDER BY 子句和其他排序操作。

1. 在 `Stream 2` 中，查询在 `Segment 4` 和 `Segment 5` 中运行网络操作，以将中间结果发送到领导节点以待进一步处理。

查询的最后一个分段返回数据。如果聚合或对返回集进行排序，则计算节点将各自的中间结果段发送到领导节点。然后，领导节点合并数据，以便将最终结果发送回请求客户端。

有关 EXPLAIN 运算符的更多信息，请参阅[EXPLAIN](r_EXPLAIN.md)。

# 影响查询性能的因素
<a name="c-query-performance"></a>

有很多因素会影响查询性能。数据、集群和数据库操作的以下方面都在查询处理速度方面发挥作用。
+ **节点、处理器或切片的数量** – 一个计算节点分为多个切片。节点越多意味着处理器和切片越多，通过跨各个切片并发运行查询的多个部分，可加快查询的处理速度。但是，节点越多也意味着花费越高，因此，您需要为自己的系统找到成本和性能之间的适当平衡点。有关 Amazon Redshift 集群架构的更多信息，请参阅[数据仓库系统架构](c_high_level_system_architecture.md)。
+ **节点类型** – Amazon Redshift 集群有多种节点类型可以选择。每种节点类型都提供不同的大小和限制，以帮助您适当地扩展集群。节点大小决定集群中每个节点的存储容量、内存、CPU 和价格。有关节点类型的更多信息，请参阅《Amazon Redshift 管理指南》**中的 [Amazon Redshift 集群概述](https://docs.aws.amazon.com/redshift/latest/mgmt/working-with-clusters.html#working-with-clusters-overview)。
+ **数据分配** – Amazon Redshift 根据表的分配方式在计算节点上存储表数据。在执行查询时，查询优化程序根据执行联接和聚合的需要将数据重新分配到计算节点。为表选择正确的分配方式有助于通过在执行联接前将数据放在需要的位置来最大程度地减小重新分配步骤的影响。有关更多信息，请参阅 [用于优化查询的数据分配](t_Distributing_data.md)。
+ **数据排序顺序** – Amazon Redshift 根据表的排序键将表数据按照排序顺序存储在磁盘中。查询优化程序和查询处理器使用有关数据所在位置的信息来减少需要扫描的数据块数，从而提高查询速度。有关更多信息，请参阅 [排序键](t_Sorting_data.md)。
+ **数据集大小** – 集群中的数据量越大，则需要扫描和重新分配的行数也越多，这会降低查询性能。您可以通过定期对数据进行 vacuum 操作和归档以及使用谓词来限制查询数据集来减轻这种影响。
+ **并发操作** – 同时运行多个操作会影响查询性能。每个操作在可用查询队列中都会占用一个或多个插槽，并使用与这些插槽关联的内存。如果其他操作正在运行，则可能没有充足的查询队列槽可用。在这种情况下，查询必须等待有槽回收后才能开始处理。有关创建和配置查询队列的更多信息，请参阅[工作负载管理](cm-c-implementing-workload-management.md)。
+ **查询结构** – 查询的编写也会影响其性能。在满足需求的前提下，请尽量将查询编写为处理和返回尽量少的数据。有关更多信息，请参阅 [设计查询的 Amazon Redshift 最佳实践](c_designing-queries-best-practices.md)。
+ **代码编译**：Amazon Redshift 为每个查询执行计划生成和编译优化的代码。编译代码执行更快，因为它消除了使用解释器的开销。为了最大限度地减少新查询的延迟，同时保留编译代码的性能优势，Amazon Redshift 使用一种称为合成的技术。合成可生成预先存在的逻辑的轻量级排列以便立即处理新查询，同时在后台编译高度优化的查询专用代码。这会将编译从查询执行的关键路径中移除，因此，新查询可以更快地启动并提供与后续运行一致的性能。

  Amazon Redshift 还使用无服务器编译服务将查询编译扩展到 Amazon Redshift 集群的计算资源之外。编译的代码段既本地缓存于集群上，也缓存于一个几乎无限的远程缓存中，该缓存在集群重启后持续存在。相同查询的后续执行可以更快地运行，因为它们可以跳过编译阶段。通过使用可扩展的编译服务，Amazon Redshift 并行编译代码，以提供始终如一的快速性能。