阅读 Aurora DSQL EXPLAIN 计划 - Amazon Aurora DSQL

阅读 Aurora DSQL EXPLAIN 计划

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

全面扫描示例

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%' 条件,这会降低效率。将更具选择性的筛选条件推送到存储层或索引层通常可以提高性能。

仅限索引扫描示例

仅限索引扫描是 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 在数据移动后最后运行,因此效率最低。其中,索引条件的性能最佳,因为它直接控制扫描的数据量。

索引扫描示例

索引扫描与仅限索引扫描类似,不同之处在于前者需要另一个步骤,即调用基表。由于 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 字段通常可以消除这种查找并提高性能。

最佳实践

  • 将筛选条件与索引列对齐,以便更早地推送筛选。

  • 使用 INCLUDE 列以支持仅限索引扫描并避免查找。

  • 使统计数据保持最新,以确保成本和行估算值准确无误。

  • 避免对大型表进行未编入索引的查询,以防止代价高昂的全面扫描。