Aurora DSQL EXPLAIN 计划中的批量嵌套循环联接
当查询将较小的外层输入与 Aurora DSQL 可从存储中高效扫描的内层输入联接时,Aurora DSQL 可以选择 Nested Loop (Batched Join) 计划。此联接类型通过先对多个外层行进行分组,再对内层进行探测,以此减少计算层与存储层之间的往返次数。
标准嵌套循环每次处理一个外层行,并对每个行重新运行一次内层扫描。批量嵌套循环联接会收集一批外层行,为整个批次构建内层扫描工作,然后将返回的内层行联接回匹配的外层行。
批量嵌套循环联接的工作原理
-
Aurora DSQL 从联接的外层侧读取一批行。
-
如果内层侧是
Index Scan或Index Only Scan,并具有已在外层侧参数化的Index Cond,则 Aurora DSQL 会为每个外层行绑定该条件,并将生成的索引扫描键合并为一次批量内层扫描。其他联接谓词不参与内层扫描。 -
不带外层参数化的
Index Cond的Index Scan或Index Only Scan的行为类似于Full Scan或Sequential Scan。Aurora DSQL 没有可合并的参数化索引扫描键,并且每个外层批次执行一次内层扫描,而不是每个外层行执行一次内层扫描。 -
Aurora DSQL 会在该批次的内层行到达时流式读取它们,而不会先物化该批次的完整内层结果,然后再进行联接。
-
当内层
Index Scan或Index Only Scan具有外层参数化的Index Cond时,Aurora DSQL 会使用Recheck Cond将每个返回的内层行匹配回该批次中对应的外层行。之后,Aurora DSQL 会应用所有其余联接谓词。如果没有外层参数化的Index Cond,Aurora DSQL 会在将每个流式内层行与外层批次匹配时,直接应用联接谓词。 -
对于左联接和反联接,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 可能会在返回最终结果集之前添加显式排序。