

# Aurora DSQL EXPLAIN プランの読み取り
<a name="reading-dsql-explain-plans"></a>

EXPLAIN プランの読み方を理解することは、クエリのパフォーマンスを最適化するうえで重要です。このセクションでは、Aurora DSQL クエリプランの実際の例を説明し、さまざまなスキャンタイプの動作を示し、フィルターが適用される場所を説明し、最適化の機会を強調します。

## これらの例で使用するサンプルテーブル
<a name="explain-plan-sample-tables"></a>

以下の例では、`transaction` と `account` の 2 つのテーブルを参照します。

`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 と機能的に同じシーケンシャルスキャンとフルスキャンの両方があります。これら 2 つの唯一の違いは、フルスキャンはストレージで追加のフィルタリングを利用できることです。このため、ほとんどの場合、上記のシーケンシャルスキャンが選択されます。類似しているため、より魅力的なフルスキャンの例のみを取り上げます。

フルスキャンは、主にプライマリキーのないテーブルで使用されます。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)
```

このプランには、異なるステージに適用される 2 つのフィルターが表示されます。`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 はプランを 2 つのノードとして示します。このように、インクルード列の追加がストレージから返される行に関してどの程度役立つかを明確に確認できます。

```
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` を手動で実行して統計を即座に更新できます。
+ 大きなテーブルに対する**インデックス化されていないクエリを回避し**、ワークロードの高いフルスキャンを防ぎます。