View a markdown version of this page

Aurora DSQL EXPLAIN 计划中的批量嵌套循环联接 - Amazon Aurora DSQL

Aurora DSQL EXPLAIN 计划中的批量嵌套循环联接

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

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

批量嵌套循环联接的工作原理

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

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

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

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

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

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

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

Aurora DSQL 何时使用此联接

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

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

SET dsql.enable_batched_nestloop = off;

如何读取批量嵌套循环联接输出

以下查询使用来自阅读 Aurora DSQL EXPLAIN 计划的示例 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 可能会在返回最终结果集之前添加显式排序。