

# Aurora DSQL EXPLAIN 计划中的批量嵌套循环联接
<a name="batched-nested-loop-joins"></a>

当查询将较小的外层输入与 Aurora DSQL 可从存储中高效扫描的内层输入联接时，Aurora DSQL 可以选择 `Nested Loop (Batched Join)` 计划。此联接类型通过先对多个外层行进行分组，再对内层进行探测，以此减少计算层与存储层之间的往返次数。

标准嵌套循环每次处理一个外层行，并对每个行重新运行一次内层扫描。批量嵌套循环联接会收集一批外层行，为整个批次构建内层扫描工作，然后将返回的内层行联接回匹配的外层行。

## 批量嵌套循环联接的工作原理
<a name="batched-nested-loop-joins-how-it-works"></a>

1. Aurora DSQL 从联接的外层侧读取一批行。

1. 如果内层侧是 `Index Scan` 或 `Index Only Scan`，并具有已在外层侧参数化的 `Index Cond`，则 Aurora DSQL 会为每个外层行绑定该条件，并将生成的索引扫描键合并为一次批量内层扫描。其他联接谓词不参与内层扫描。

1. 不带外层参数化的 `Index Cond` 的 `Index Scan` 或 `Index Only Scan` 的行为类似于 `Full Scan` 或 `Sequential Scan`。Aurora DSQL 没有可合并的参数化索引扫描键，并且每个外层批次执行一次内层扫描，而不是每个外层行执行一次内层扫描。

1. Aurora DSQL 会在该批次的内层行到达时流式读取它们，而不会先物化该批次的完整内层结果，然后再进行联接。

1. 当内层 `Index Scan` 或 `Index Only Scan` 具有外层参数化的 `Index Cond` 时，Aurora DSQL 会使用 `Recheck Cond` 将每个返回的内层行匹配回该批次中对应的外层行。之后，Aurora DSQL 会应用所有其余联接谓词。如果没有外层参数化的 `Index Cond`，Aurora DSQL 会在将每个流式内层行与外层批次匹配时，直接应用联接谓词。

1. 对于左联接和反联接，Aurora DSQL 会跟踪哪些外层行已匹配，并在批次的内层扫描完成后输出未匹配的行。

当内层侧是带有参数化的 `Index Cond` 的索引扫描时，此方法最有效，因为合并后的扫描键可为整个外层批次提供有针对性的存储访问。

## Aurora DSQL 何时使用此联接
<a name="batched-nested-loop-joins-when-used"></a>

当联接的内层侧是物理扫描节点（即 `Index Scan`、`Index Only Scan`、`Full Scan` 或 `Sequential Scan`），且批处理的预计成本低于为每个外层行运行内层扫描的成本时，Aurora DSQL 会考虑批量嵌套循环联接。在实践中，最大的收益通常来自较小的外层输入与带外层侧参数化 `Index Cond` 的内层索引扫描。

如果另一个计划形态的成本更低（例如哈希联接或合并联接），则 Aurora DSQL 会转而选择该计划。要在调优期间比较各种计划形态，您可以为当前会话禁用批量嵌套循环联接：

```
SET dsql.enable_batched_nestloop = off;
```

## 如何读取批量嵌套循环联接输出
<a name="batched-nested-loop-joins-reading-output"></a>

以下查询使用来自[阅读 Aurora DSQL EXPLAIN 计划](reading-dsql-explain-plans.md)的示例 `transaction` 和 `account` 表：

```
EXPLAIN
SELECT t.account_id, a.balance
FROM transaction t
LEFT JOIN account a
  ON t.account_id = a.customer_id
 AND a.balance > CASE
                    WHEN t.description LIKE 'fee%' THEN 0
                    ELSE 100
                  END
WHERE t.transaction_date >= '2025-01-01'
  AND (a.status = 'active' OR a.customer_id IS NULL)
ORDER BY t.account_id;
```

EXPLAIN 计划可包含类似于以下内容的输出：

```
Sort
  Sort Key: t.account_id
  -> Nested Loop (Batched Join)
       Filter: (((status)::text = 'active'::text) OR (customer_id IS NULL))
       Join Type: Left
       Recheck Cond: (customer_id = t.account_id)
       Join Filter: (balance > CASE WHEN (t.description ~~ 'fee%'::text) THEN '0'::numeric ELSE '100'::numeric END)
       -> Full Scan (btree-table) on transaction t
            -> Storage Scan on transaction t
                 Filters: (transaction_date >= '2025-01-01 00:00:00'::timestamp without time zone)
                 -> B-Tree Scan on transaction t
       -> Index Only Scan using idx1 on account a
            Index Cond: (customer_id = t.account_id)
```

`Nested Loop (Batched Join)`  
表明 Aurora DSQL 在探测联接的内层侧之前，会先对外层行进行批处理。

`Index Cond`  
显示用于为当前批次探测内层侧的谓词。当内层侧为 `Index Scan` 或 `Index Only Scan` 时，此谓词通常引用联接外层侧的列。

`Filter`  
显示应用于联接结果的条件。在此示例中，`WHERE (a.status = 'active' OR a.customer_id IS NULL)` 条件会出现在此处。按照标准 SQL 左联接语义，此条件无法推入内层扫描中，因为这样做会更改查询结果。Aurora DSQL 将它显示为联接节点上的筛选条件。相比之下，针对 `t.transaction_date` 的条件仅引用外层表，因此它显示在外层物理扫描的下方。

`Join Type`  
显示批量联接的联接语义，例如 `Left`、`Semi` 或 `Anti`。

`Recheck Cond`  
仅在内层侧为 `Index Scan` 或 `Index Only Scan`，且至少有一个在联接外层侧参数化的 `Index Cond` 时出现。对于每个返回的内层行，Aurora DSQL 会对批次中的每个外层行执行重新检查，以确定哪些外层行生成了该探测并且应与该内层行联接。

`Join Filter`  
显示 Aurora DSQL 在将内层行匹配回批次中的候选外层行后所评估的联接谓词。这些谓词用于确定要联接的行对，但它们不会像 `Index Cond` 那样参与存储探测。

前置排序节点  
当查询需要有序输出时可能会出现。批量嵌套循环联接无法提供与标准嵌套循环相同的输出顺序保证，因此 Aurora DSQL 可能会在返回最终结果集之前添加显式排序。