

# 使用 Aurora DSQL EXPLAIN 解释计划
<a name="working-with-explain-plans"></a>

Aurora DSQL 使用与 PostgreSQL 类似的 EXPLAIN 计划结构，但增加了一些反映其分布式架构和执行模型的关键内容。

在本文档中，我们将概述 Aurora DSQL EXPLAIN 计划，重点介绍与 PostgreSQL 相比的相似之处和不同之处。我们将介绍 Aurora DSQL 中可用的各种类型的扫描操作，并协助您了解运行查询的成本。

## PostgreSQL 与 Aurora DSQL EXPLAIN 计划
<a name="postgresql-explain-plans"></a>

 Aurora DSQL 建立在 PostgreSQL 数据库基础之上，与 PostgreSQL 共享大多数计划结构，但存在影响查询执行和优化的关键架构差异：


| 功能 | PostgreSQL | Aurora DSQL | 
| --- | --- | --- | 
|  数据存储  |  堆存储  |  没有堆，所有行都通过唯一标识符编制索引  | 
|  主键  |  主键索引与表数据是分开的  |  主键索引是所有额外列均为 INCLUDE 列的表  | 
|  二级索引  |  标准二级索引  |  工作原理与 PostgreSQL 相同，能够包含非键列  | 
|  筛选功能  |  索引条件、堆筛选条件  |  索引条件、存储筛选条件、查询处理器筛选条件   | 
|  扫描类型  |  顺序扫描、索引扫描、仅限索引扫描  |  全面扫描、仅限索引扫描、索引扫描  | 
|  查询执行  |  对数据库为本地的  |  分布式（计算和存储是分开的）  | 

Aurora DSQL 直接按主键顺序存储表数据，而不是存储在单独的堆中。每行都由一个唯一键（通常是主键）标识，这使数据库能够更高效地优化查找。架构差异解释了为什么 Aurora DSQL 经常在 PostgreSQL 可能选择顺序扫描的情况下使用仅限索引扫描。

另一个关键区别是，Aurora DSQL 将计算与存储分开，从而可以在执行路径中更早地应用筛选条件，以减少数据移动并提高性能。

有关在 PostgreSQL 中使用 EXPLAIN 计划的更多信息，请参阅 [PostgreSQL EXPLAIN 文档](https://www.postgresql.org/docs/current/using-explain.html)。

## Aurora DSQL EXPLAIN 中的关键元素
<a name="explain-plan-elements"></a>

Aurora DSQL EXPLAIN 计划提供有关如何执行查询的详细信息，包括筛选发生的位置以及从存储中检索哪些列。了解此输出有助于优化查询性能。

索引条件  
用于导航索引的条件。效率最高的筛选，可减少扫描的数据。在 Aurora DSQL 中，可以在执行计划的多个层面上应用索引条件。

投影  
从存储中检索的列。预测越少意味着性能越好。

存储筛选条件  
在存储级别上应用的条件。比查询处理器筛选条件更高效。

查询处理器筛选条件  
在查询处理器级别应用的条件。需要在筛选之前传输所有数据，这会导致更高的数据移动和处理开销。

## Aurora DSQL 中的筛选条件
<a name="filtering-and-projection"></a>

Aurora DSQL 将计算与存储分开，这意味着在查询执行期间应用筛选条件的位置会对性能产生重大影响。在传输大量数据之前应用筛选的条件可减少延迟并提高效率。越早应用筛选条件，需要处理、移动和扫描的数据就越少，从而加快查询速度。

Aurora DSQL 可以在查询路径的多个阶段应用筛选条件。了解这些阶段是解释查询计划和优化性能的关键。


| 级别 | 筛选条件类型 | 描述 | 
| --- | --- | --- | 
| 1 | 索引条件 |  在扫描索引时应用。限制从存储中读取的数据量，并减少发送到计算层的数据。  | 
| 2 | 存储筛选条件 | 在从存储中读取数据之后但在将其发送到计算之前应用。这里的一个例子是针对索引的包含列的筛选条件。减少数据传输，但不会减少读取量。 | 
| 3 | 查询处理器筛选条件 | 在数据到达计算层后应用。必须先传输所有数据，这会增加延迟和成本。目前，Aurora DSQL 无法对存储执行所有筛选和投影操作，因此某些查询可能会被迫回退到这种类型的筛选。 | 

# 阅读 Aurora DSQL EXPLAIN 计划
<a name="reading-dsql-explain-plans"></a>

了解如何阅读 EXPLAIN 计划是优化查询性能的关键。在本节中，我们将介绍 Aurora DSQL 查询计划的真实示例，展示不同扫描类型的行为，解释应用筛选条件的位置，并重点介绍优化的机会。

## 这些示例中使用的示例表
<a name="explain-plan-sample-tables"></a>

下面的示例引用两个表：`transaction` 和 `account`。

`transaction` 表没有主键，这将导致 Aurora DSQL 在查询表时执行全表扫描。

`account` 表对 `customer_id` 具有索引。该索引包括 `balance` 和 `status` 作为覆盖列，支持直接从索引中满足某些查询，而无需从基表中读取。但是，索引不包括 `created_at`，因此，引用该列的查询需要额外的表访问权限。

```
CREATE TABLE transaction (
    account_id uuid,
    transaction_date timestamp,
    description text
);

CREATE TABLE account (
    customer_id uuid,
    balance numeric,
    status varchar,
    created_at timestamp
);

CREATE INDEX ASYNC idx1 ON account (customer_id) INCLUDE (balance, status);
```

## 全面扫描示例
<a name="full-scan-example"></a>

Aurora DSQL 既有顺序扫描（功能上与 PostgreSQL 相同），也有全面扫描。这两者之间的唯一区别是，全面扫描可以对存储进行额外的筛选。因此，与顺序扫描相比，几乎始终优先选择全面扫描。由于相似性，我们将只介绍更有趣的全面扫描的示例。

全面扫描将主要用于没有主键的表。由于 Aurora DSQL 主键默认情况下为完全覆盖索引，因此在 PostgreSQL 使用顺序扫描的许多情况下，Aurora DSQL 很可能会对主键使用仅限索引扫描。与大多数其它数据库一样，没有索引的表的扩展性会很差。

```
EXPLAIN SELECT account_id FROM transaction WHERE transaction_date > '2025-01-01' AND description LIKE '%external%';
```

```
                                                   QUERY PLAN
----------------------------------------------------------------------------------------------------------------
 Full Scan (btree-table) on transaction  (cost=125100.05..177933.38 rows=33333 width=16)
   Filter: (description ~~ '%external%'::text)
   -> Storage Scan on transaction (cost=12510.05..17793.38 rows=66666 width=16)
        Projections: account_id, description
        Filters: (transaction_date > '2025-01-01 00:00:00'::timestamp without time zone)
        -> B-Tree Scan on transaction (cost=12510.05..17793.38 rows=100000 width=30)
```

此计划显示了在不同阶段应用的两个筛选条件。`transaction_date > '2025-01-01'` 条件应用于存储层，从而减少返回的数据量。稍后，在数据传输之后，在查询处理器中应用 `description LIKE '%external%'` 条件，这会降低效率。将更具选择性的筛选条件推送到存储层或索引层通常可以提高性能。

## 仅限索引扫描示例
<a name="index-only-scan-example"></a>

仅限索引扫描是 Aurora DSQL 中最优的扫描类型，因为它们可以最大限度地减少到存储层的往返次数，并且可以进行最多的筛选。但仅因为您看到了仅限索引扫描，并不意味着您拥有最好的计划。由于可能发生的筛选级别各不相同，因此仍要注意可能发生筛选的不同位置，这一点至关重要。

```
EXPLAIN SELECT balance FROM account 
WHERE customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb' 
AND balance > 100 
AND status = 'pending';
```

```
                                  QUERY PLAN
-------------------------------------------------------------------------------
 Index Only Scan using idx1 on account  (cost=725.05..1025.08 rows=8 width=18)
   Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
   Filter: (balance > '100'::numeric)
   -> Storage Scan on idx1 (cost=12510.05..17793.38 rows=9 width=16)
        Projections: balance
        Filters: ((status)::text = 'pending'::text)
        -> B-Tree Scan on idx1 (cost=12510.05..17793.38 rows=10 width=30)
            Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
```

在此计划中，首先在索引扫描期间评估索引条件 (`customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'`)，这是效率最高的阶段，因为它会限制从存储中读取的数据量。存储筛选条件 `status = 'pending'` 是在读取数据之后但在将数据发送到计算层之前应用的，从而减少了传输的数据量。最后，查询处理器筛选条件 `balance > 100` 在数据移动后最后运行，因此效率最低。其中，索引条件的性能最佳，因为它直接控制扫描的数据量。

## 索引扫描示例
<a name="index-scan-example"></a>

索引扫描与仅限索引扫描类似，不同之处在于前者需要另一个步骤，即调用基表。由于 Aurora DSQL 可以指定存储筛选条件，因此它能够对索引调用和查找调用指定存储筛选条件。

为了明确这一点，Aurora DSQL 将计划呈现为两个节点。这样，您可以清楚地看到添加包含列对从存储返回的行有多大帮助。

```
EXPLAIN SELECT balance FROM account 
WHERE customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'
AND balance > 100 
AND status = 'pending' 
AND created_at > '2025-01-01';
```

```
                                                QUERY PLAN
----------------------------------------------------------------------------------------------------------
 Index Scan using idx1 on account  (cost=728.18..1132.20 rows=3 width=18)
   Filter: (balance > '100'::numeric)
   Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
   -> Storage Scan on idx1 (cost=12510.05..17793.38 rows=8 width=16)
        Projections: balance
        Filters: ((status)::text = 'pending'::text)
        -> B-Tree Scan on account (cost=12510.05..17793.38 rows=10 width=30)
            Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
   -> Storage Lookup on account (cost=12510.05..17793.38 rows=4 width=16)
        Filters: (created_at > '2025-01-01 00:00:00'::timestamp without time zone)
        -> B-Tree Lookup on transaction (cost=12510.05..17793.38 rows=8 width=30)
```

 此计划显示了筛选是如何跨多个阶段发生的：
+  有关 `customer_id ` 的索引条件会尽早筛选数据。
+ 有关 `status` 的存储筛选条件会进一步缩小结果范围，然后再将结果发送到计算中。
+ 有关 `balance` 的查询处理器筛选条件将在稍后传输后应用。
+ 从基表中提取其它列时，会评估有关 `created_at` 的查找筛选条件。

将常用列添加为 `INCLUDE` 字段通常可以消除这种查找并提高性能。

## 最佳实践
<a name="best-practices"></a>
+ **将筛选条件与索引列对齐**，以便更早地推送筛选。
+ **使用 INCLUDE 列**以支持仅限索引扫描并避免查找。
+ 在调查性能问题时**验证行估计值**。Aurora DSQL 根据数据更改率在后台运行 `ANALYZE`，从而自动管理统计数据。如果估计值看起来不准确，则可以手动运行 `ANALYZE` 以立即刷新统计数据。
+ **避免对大型表进行未编入索引的查询**，以防止代价高昂的全面扫描。

# 了解 EXPLAIN ANALYZE 中的 DPU
<a name="understanding-dpus-explain-analyze"></a>

Aurora DSQL 在 `EXPLAIN ANALYZE VERBOSE` 计划输出中提供**语句级**分布式处理单元（DPU）信息，以便您能够更深入地了解开发过程中的查询成本。此部分将阐释 DPU 的定义以及如何在 `EXPLAIN ANALYZE VERBOSE` 输出中解读它们。

## 什么是 DPU？
<a name="what-is-dpu"></a>

分布式处理单元（DPU）是 Aurora DSQL 中用于量化工作完成量的标准化度量单位，由以下部分组成：
+ **ComputeDPU** – 执行 SQL 查询所花费的时间
+ **ReadDPU** – 从存储中读取数据所使用的资源
+ **WriteDPU** – 向存储中写入数据所使用的资源
+ **MultiRegionWriteDPU** – 用于在多区域配置中将写入内容复制到对等集群的资源。

## EXPLAIN ANALYZE VERBOSE 中的 DPU 使用量
<a name="dpu-usage-explain-analyze"></a>

Aurora DSQL 对 `EXPLAIN ANALYZE VERBOSE` 进行了扩展，以便在输出末尾包含语句级 DPU 使用量估算值。这可让您即时了解查询成本，并帮助您识别工作负载成本驱动因素、优化查询性能，并更准确地预测资源使用量。

以下示例展示如何解释 EXPLAIN ANALYZE VERBOSE 输出中包含的语句级 DPU 估算值。

### 示例 1：SELECT 查询
<a name="select-query-example"></a>

```
EXPLAIN ANALYZE VERBOSE SELECT * FROM test_table;
```

```
QUERY PLAN
----------------------------------------------------
Index Only Scan using test_table_pkey on public.test_table  (cost=125100.05..171100.05 rows=1000000 width=36) (actual time=2.973..4.482 rows=120 loops=1)
  Output: id, context
  -> Storage Scan on test_table_pkey (cost=125100.05..171100.05 rows=1000000 width=36) (actual rows=120 loops=1)
      Projections: id, context
      -> B-Tree Scan on test_table_pkey (cost=125100.05..171100.05 rows=1000000 width=36) (actual rows=120 loops=1)
Query Identifier: qymgw1m77maoe
Planning Time: 11.415 ms
Execution Time: 4.528 ms
Statement DPU Estimate:
  Compute: 0.01607 DPU
  Read: 0.04312 DPU
  Write: 0.00000 DPU
  Total: 0.05919 DPU
```

在此示例中，SELECT 语句执行了一次仅索引扫描，因此大部分成本来自读取 DPU（0.04312）和计算 DPU（0.01607），前者表示从存储中检索数据所使用的资源，后者反映处理并返回结果所使用的计算资源。由于该查询未修改数据，因此不存在写入 DPU。总 DPU（0.05919）为计算 DPU、读取 DPU 与写入 DPU 的总和。

### 示例 2：INSERT 查询
<a name="insert-query-example"></a>

```
EXPLAIN ANALYZE VERBOSE INSERT INTO test_table VALUES (1, 'name1'), (2, 'name2'), (3, 'name3');
```

```
QUERY PLAN
----------------------------------------------------
Insert on public.test_table  (cost=0.00..0.04 rows=0 width=0) (actual time=0.055..0.056 rows=0 loops=1)
  ->  Values Scan on "*VALUES*"  (cost=0.00..0.04 rows=3 width=122) (actual time=0.003..0.008 rows=3 loops=1)
        Output: "*VALUES*".column1, "*VALUES*".column2
Query Identifier: jtkjkexhjotbo
Planning Time: 0.068 ms
Execution Time: 0.543 ms
Statement DPU Estimate:
  Compute: 0.01550 DPU
  Read: 0.00307 DPU (Transaction minimum: 0.00375)
  Write: 0.01875 DPU (Transaction minimum: 0.05000)
  Total: 0.03732 DPU
```

此语句主要执行写入操作，因此大部分成本与写入 DPU 相关。计算 DPU（0.01550）表示处理并插入值所使用的计算资源。读取 DPU（0.00307）反映系统轻量级读取操作（如目录查询或索引检查）所使用的资源。

请注意读取 DPU 和写入 DPU 旁边显示的事务最低计费标准。这些值代表单次事务的基准成本，*仅在操作涉及数据读取或写入时适用*。这并不意味着每个事务都会自动产生 0.00375 读取 DPU 或 0.05 写入 DPU 的费用。相反，这些最低计费标准仅在事务级别进行成本聚合时适用，且仅当该事务内实际发生读取或写入操作时生效。由于范围差异，`EXPLAIN ANALYZE VERBOSE` 中的语句级估算值可能与 CloudWatch 或计费数据中报告的事务级指标不完全一致。

## 利用 DPU 信息进行优化
<a name="using-dpu-information-optimization"></a>

单语句 DPU 估算为您提供了一种强大的查询优化方式，其价值不仅限于缩短执行时间。常见使用案例包括：
+ **成本感知：**了解某一查询相对于其他查询的成本高低。
+ **架构优化：**比较索引或架构变更对性能与资源效率产生的影响。
+ **预算规划：**基于观测到的 DPU 使用量来估算工作负载成本。
+ **查询比较：**根据相对 DPU 使用量来评估替代查询方法。

## 解释 DPU 信息
<a name="interpreting-dpu-information"></a>

使用 `EXPLAIN ANALYZE VERBOSE` 中的 DPU 数据时，请记住以下最佳实践：
+ **定向地使用 DPU 数据：**将报告的 DPU 值视为了解查询*相对*成本的依据，而非与 CloudWatch 指标或计费数据完全一致的精确值。预计会出现差异，因为 `EXPLAIN ANALYZE VERBOSE` 报告的是语句级成本，而 CloudWatch 聚合的是事务级活动。此外，CloudWatch 还包括 `EXPLAIN ANALYZE VERBOSE` 有意排除的后台操作（例如 ANALYZE 或压缩）和事务开销（`BEGIN`/`COMMIT`）。
+ 在分布式系统中，**DPU 在不同的运行间存在波动是正常现象**，并不表示出现错误。缓存、执行计划更改、并发性或数据分布偏移等因素都可能导致同一查询在不同的运行时消耗的资源量存在差异。
+ **批量处理小型操作：**如果您的工作负载发出许多小型语句，建议将其批量合并为大型操作（不超过 10 MB）。这可减少四舍五入开销，并生成更具参考价值的成本估算值。
+ **用于调优，而非计费：**`EXPLAIN ANALYZE VERBOSE` 中的 DPU 数据专为成本感知、查询调优和优化设计，并非计费级指标。要获取权威的成本和使用量数据，请始终以 CloudWatch 指标或月度账单报告为准。