亚马逊AWS官方博客

在 Amazon Athena 中使用 EXPLAIN 和 EXPLAIN ANALYZE 优化联合查询性能

Amazon Athena 是一种交互式查询服务,可使用标准 SQL 轻松分析 Amazon Simple Storage Service(Amazon S3)中的数据。Athena 是一种无服务器服务,因此您无需管理任何基础设施,而且只需为所运行的查询付费。2019 年,Athena 增加了对联合查询的支持,以便跨存储在关系、非关系、对象和自定义数据来源中的数据运行 SQL 查询。

2021 年,Athena 增加了对 EXPLAIN 语句的支持,可以帮助您理解和提高查询的效率。EXPLAIN 语句提供查询运行计划的详细细分。您可以对计划进行分析,以确定和降低查询复杂性,并改善其运行时间。您还可以在运行查询之前使用 EXPLAIN 验证 SQL 语法。这样做有助于防止在运行查询时可能发生的错误。

Athena 还增加了 EXPLAIN ANALYZE,可以显示查询的计算成本及其运行计划。管理员可以从使用 EXPLAIN ANALYZE 中受益,因为它提供了扫描数据的计数,可帮助您减少用户查询带来的财务影响,并应用优化以更好地控制成本。

在这篇博文中,我们将演示如何使用和解释 EXPLAIN 和 EXPLAIN ANALYZE 语句,来提高查询多个数据来源时的 Athena 查询性能。

解决方案概览

为了演示如何使用 EXPLAIN 和 EXPLAIN ANALYZE 语句,我们使用以下服务和资源:

Athena 使用 AWS Glue 数据目录在您的 AWS 账户中存储和检索 Amazon S3 数据的表元数据。表元数据让 Athena 查询引擎知道如何查找、读取和处理要查询的数据。我们使用 Athena 数据来源连接器连接到 Amazon S3 外部数据来源。

先决条件

要部署 CloudFormation 模板,您必须具备以下内容:

使用 AWS CloudFormation 预调配资源

要部署 CloudFormation 模板,请完成以下步骤:

  1. 选择 Launch Stack(启动堆栈):

  1. 按照 AWS CloudFormation 控制台上的提示创建堆栈。
  2. 注意堆栈的 Outputs(输出)选项卡上的键值对。

配置 Athena 数据来源连接器时使用这些值。

CloudFormation 模板会创建以下资源:

  • S3 存储桶用于存储数据并充当 Lambda 的临时溢出存储桶
  • 用于 S3 存储桶中数据的 AWS Glue 数据目录表
  • 一个 DynamoDB 表和 Amazon RDS for MySQL 表,用于连接来自不同来源的多个表
  • Amazon RDS for MySQL 和 DynamoDB 需要的 VPC、子网和端点

下图显示了数据加载的高级数据模型。

创建 DynamoDB 数据来源连接器

要为 Athena 创建 DynamoDB 连接器,请完成以下步骤:

  1. 在 Athena 控制台的导航窗格中,选择 Data sources(数据来源)。
  2. 选择 Create data source(创建数据来源)。
  3. 对于 Data sources(数据来源),选择 Amazon DynamoDB
  4. 选择 Next(下一步)。

  1. 对于 Data source name(数据来源名称),输入 DDB。

  1. 对于 Lambda function(Lambda 函数),选择 Create Lambda function(创建 Lambda 函数)。

这时将会在您的浏览器中打开一个新选项卡。

  1. 对于 Application name(应用程序名称),输入 AthenaDynamoDBConnector
  2. 对于 SpillBucket,输入 AthenaSpillBucket 的 CloudFormation 堆栈中的值。
  3. 对于 AthenaCatalogName,输入 dynamodb-lambda-func
  4. 将剩余值保留为默认值。
  5. 选择 I acknowledge that this app creates custom IAM roles and resource policies(我确认此应用程序创建了自定义 IAM 角色和资源策略)。
  6. 选择 Deploy(部署)。

您将返回到 Athena 控制台上的 Connect data sources(连接数据来源)部分。

  1. 选择 Lambda function(Lambda 函数)旁边的刷新图标。
  2. 选择您刚刚创建的 Lambda 函数(dynamodb-lambda-func)。

  1. 选择 Next(下一步)。
  2. 查看设置并选择 Create data source(创建数据来源)。
  3. 如果您尚未设置 Athena 查询结果位置,请选择 Athena 查询编辑器页面上的 View settings(查看设置)。

  1. 选择 Manage(管理)。
  2. 对于 Location of query result(查询结果的位置),浏览 CloudFormation 模板中为 Athena 溢出存储桶指定的 S3 存储桶。
  3. 将 Athena-query 添加到 S3 路径中。
  4. 选择 Save(保存)。

  1. 在 Athena 查询编辑器中,对于 Data source(数据来源),选择 DDB
  2. 对于 Database(数据库),选择 default(默认)。

现在,您可以浏览 sportseventinfo 表的模式;DynamoDB 中的数据是相同的。

  1. 选择 sportseventinfo 表的选项图标,然后选择 Preview Table(预览表)。

创建 Amazon RDS for MySQL 数据来源连接器

现在我们为 Amazon RDS for MySQL 创建连接器。

  1. 在 Athena 控制台的导航窗格中,选择 Data sources(数据来源)。
  2. 选择 Create data source(创建数据来源)。
  3. 对于 Data sources(数据来源),选择 MySQL
  4. 选择 Next(下一步)。

  1. 对于 Data source name(数据来源名称),输入 MySQL。

  1. 对于 Lambda function(Lambda 函数),选择 Create Lambda function(创建 Lambda 函数)。

  1. 对于 Application name(应用程序名称),输入 AthenaMySQLConnector
  2. 对于 SecretNamePrefix,输入 AthenaMySQLFederation
  3. 对于 SpillBucket,输入 AthenaSpillBucket 的 CloudFormation 堆栈中的值。
  4. 对于 DefaultConnectionString,输入 MySQLConnection 的 CloudFormation 堆栈中的值。
  5. 对于 LambdaFunctionName,输入 mysql-lambda-func
  6. 对于 SecurityGroupIds,输入 RDSSecurityGroup 的 CloudFormation 堆栈中的值。
  7. 对于 SubnetIds,输入 RDSSubnets 的 CloudFormation 堆栈中的值。
  8. 选择 I acknowledge that this app creates custom IAM roles and resource policies(我确认此应用程序创建了自定义 IAM 角色和资源策略)。
  9. 选择 Deploy(部署)。

  1. 在 Lambda 控制台上,打开您创建的函数(mysql-lambda-func)。
  2. 在 Configuration(配置)选项卡的 Environment variables(环境变量)下,选择 Edit(编辑)。

  1. 选择 Add environment variable(添加环境变量)。
  2. 输入新的键值对:
    • 对于 Key(键),输入 MYSQL_connection_string
    • 对于 Value(值),输入 MySQLConnection 的 CloudFormation 堆栈中的值。
  3. 选择 Save(保存)。

  1. 返回 Athena 控制台上的 Connect data sources(连接数据来源)部分。
  2. 选择 Lambda function(Lambda 函数)旁边的刷新图标。
  3. 选择您创建的 Lambda 函数(mysql-lamdba-function)

  1. 选择 Next(下一步)。
  2. 查看设置并选择 Create data source(创建数据来源)。
  3. 在 Athena 查询编辑器中,对于 Data Source(数据来源),选择 MYSQL
  4. 对于 Database(数据库),选择 sportsdata

  1. 选择表旁边的选项图标,然后选择 Preview Table(预览表)以检查数据和模式。

在以下各节中,我们将演示优化查询的不同方法。

使用 EXPLAIN 计划的最佳联接顺序

联接是一种基本 SQL 操作,它使用匹配列中的关系来查询多个表中的数据。联接操作会影响从表中读取的数据量、通过网络传输到中间阶段的数据量,以及构建哈希表以促进联接所需的内存量。

如果您有多个联接操作,但这些联接表的顺序不正确,则可能会遇到性能问题。为了演示这一点,我们使用以下来自不同来源的表,并按一定的顺序联接它们。然后,我们使用 Athena 的 EXPLAIN 功能观察查询运行时并提高性能,该功能为优化查询提供了一些建议。

您之前运行的 CloudFormation 模板将数据加载到以下服务中:

AWS 存储 表名称 行数
Amazon DynamoDB sportseventinfo 657
Amazon S3 person 7,025,585
Amazon S3 ticketinfo 2,488

我们构造一个查询,按门票类型查找所有参与活动的人。使用以下联接的查询运行时间大约需要 7 分钟才完成:

SELECT t.id AS ticket_id,
e.eventid,
p.first_name 
FROM 
"DDB"."default"."sportseventinfo" e,
"AwsDataCatalog"."athenablog"."person" p,
"AwsDataCatalog"."athenablog"."ticketinfo" t 
WHERE 
t.sporting_event_id = cast(e.eventid as double) 
AND t.ticketholder_id = p.id

现在我们在查询中使用 EXPLAIN 来查看其运行计划。我们使用与之前相同的查询,但添加了解释(TYPE DISTRIBUTED):

EXPLAIN (TYPE DISTRIBUTED)
SELECT t.id AS ticket_id,
e.eventid,
p.first_name 
FROM 
"DDB"."default"."sportseventinfo" e,
"AwsDataCatalog"."athenablog"."person" p,
"AwsDataCatalog"."athenablog"."ticketinfo" t 
WHERE 
t.sporting_event_id = cast(e.eventid as double) 
AND t.ticketholder_id = p.id

以下屏幕截图显示了我们的输出

注意片段 1 中的交叉联接。联接将转换为每个表的笛卡尔乘积,其中表中的每条记录都与另一个表中的每条记录进行比较。因此,完成此查询需要大量时间。

为了优化我们的查询,我们可以通过将联接表重新排序为首先是 sportseventinfo,其次是 ticketinfo,最后是 person 来重写。之所以出现这种情况,是因为在查询计划阶段转换为 JOIN ON 子句的 WHERE 子句在 person 表和 sportseventinfo 表之间没有联接关系。因此,查询计划生成器将联接类型转换为交叉联接(笛卡尔乘积),这会降低效率。对表进行重新排序会使 WHERE 子句与 INNER JOIN 类型对齐,该类型满足 JOIN ON 子句的要求,运行时间从 7 分钟缩短到 10 秒。

我们优化的查询的代码如下所示:

SELECT t.id AS ticket_id,
e.eventid,
p.first_name 
FROM 
"DDB"."default"."sportseventinfo" e,
"AwsDataCatalog"."athenablog"."ticketinfo" t,
"AwsDataCatalog"."athenablog"."person" p 
WHERE 
t.sporting_event_id = cast(e.eventid as double) 
AND t.ticketholder_id = p.id

以下是我们对联接子句进行重新排序后的查询的 EXPLAIN 输出:

EXPLAIN (TYPE DISTRIBUTED) 
SELECT t.id AS ticket_id,
e.eventid,
p.first_name 
FROM 
"DDB"."default"."sportseventinfo" e,
"AwsDataCatalog"."athenablog"."ticketinfo" t,
"AwsDataCatalog"."athenablog"."person" p 
WHERE t.sporting_event_id = cast(e.eventid as double) 
AND t.ticketholder_id = p.id

以下屏幕截图显示了我们的输出。

交叉联接更改为在列(eventididticketholder_id)上进行联接的 INNER JOIN,这会导致查询运行得更快。ticketinfo 和 person 表之间的联接转换为 PARTITION 分配类型,其中由于 person 表的大小,左表和右表都会在所有 Worker 节点之间进行哈希分区。sportseventinfo 表和 ticketinfo 之间的联接将转换为 REPLICATED 分配类型,其中一个表在所有 Worker 节点之间进行哈希分区,另一个表复制到所有 Worker 节点以执行联接操作。

有关如何分析这些结果的更多信息,请参阅了解 Athena EXPLAIN 语句结果

作为最佳实践,我们建议将 JOIN 语句与 ON 子句一起使用,如以下代码所示:

SELECT t.id AS ticket_id,
e.eventid,
p.first_name 
FROM 
"AwsDataCatalog"."athenablog"."person" p 
JOIN "AwsDataCatalog"."athenablog"."ticketinfo" t ON t.ticketholder_id = p.id 
JOIN "ddb"."default"."sportseventinfo" e ON t.sporting_event_id = cast(e.eventid as double)

另外,在联接两个表时,最佳做法是在联接的左侧指定较大的表,在联接的右侧指定较小的表。Athena 将右侧的表分配给 Worker 节点,然后流式传输左侧的表以进行联接。如果右侧的表较小,则占用的内存量较少,并且查询运行速度更快。

在以下各节中,我们将举例说明如何使用 EXPLAIN ANALYZE 优化筛选谓词的下推以及针对 Athena 数据来源的投影筛选操作。

Amazon RDS for MySQL 的 Athena 连接器的下推优化

下推是一种优化,通过将 SQL 查询的处理移到尽可能靠近数据的位置,从而提高 SQL 查询的性能。下推可以在通过网络传输数据之前对其进行筛选,在将数据加载到内存之前对其进行筛选,从而大大缩短 SQL 语句的处理时间。Amazon RDS for MySQL 的 Athena 连接器支持筛选谓词的下推和投影下推。

下表总结了我们用来演示使用 Aurora MySQL 进行下推的服务和表。

表名称 行数 以 KB 为单位的大小
player_partitioned 5,157 318.86
sport_team_partitioned 62 5.32

我们使用以下查询作为筛选谓词和投影筛选的示例:

SELECT full_name,
name 
FROM "sportsdata"."player_partitioned" a 
JOIN "sportsdata"."sport_team_partitioned" b ON a.sport_team_id=b.id 
WHERE a.id='1.0'

此查询根据球员的 ID 选择球员及其球队。它作为 WHERE 子句中的筛选操作和投影的示例,因为它只选择了两列。

我们使用 EXPLAIN ANALYZE 来获取运行此查询的成本:

EXPLAIN ANALYZE 
SELECT full_name,
name 
FROM "MYSQL"."sportsdata"."player_partitioned" a 
JOIN "MYSQL"."sportsdata"."sport_team_partitioned" b ON a.sport_team_id=b.id 
WHERE a.id='1.0'

以下屏幕截图显示了 player_partitioned 表的片段 2 中的输出,在该输出中,我们观察到连接器在源端有一个成功的下推筛选,因此它只尝试扫描表中 5,157 条记录中的一条记录。输出还显示查询扫描只有两列(full_name 作为投影列,sport_team_id 作为联接列),并使用 SELECT 和 JOIN,这表示投影下推成功。这有助于减少使用 Athena 数据来源连接器时的数据扫描。

现在我们看看筛选谓词下推不适用于 Athena 连接器的条件。

筛选谓词中的 LIKE 语句

我们从以下示例查询开始,演示在筛选谓词中使用 LIKE 语句:

SELECT * 
FROM "MYSQL"."sportsdata"."player_partitioned" 
WHERE first_name LIKE '%Aar%'

然后我们添加 EXPLAIN ANALYZE:

EXPLAIN ANALYZE 
SELECT * 
FROM "MYSQL"."sportsdata"."player_partitioned" 
WHERE first_name LIKE '%Aar%'

EXPLAIN ANALYZE 输出显示,尽管 WHERE 子句只有 30 条与条件 %Aar% 匹配的记录,查询会对所有记录执行表扫描(扫描包含 5,157 条记录的 player_partitioned 表)。因此,即使使用 WHERE 子句,数据扫描也会显示完整的表大小。

我们可以通过只选择所需的列来优化相同的查询:

EXPLAIN ANALYZE 
SELECT sport_team_id,
full_name 
FROM "MYSQL"."sportsdata"."player_partitioned" 
WHERE first_name LIKE '%Aar%'

从 EXPLAIN ANALYZE 输出中,我们可以观察到连接器支持投影筛选下推,因为我们只选择了两列。这使数据扫描大小缩小到表大小的一半。

筛选谓词中的 OR 语句

我们从以下查询开始,演示在筛选谓词中使用 OR 语句:

SELECT id,
first_name 
FROM "MYSQL"."sportsdata"."player_partitioned" 
WHERE first_name = 'Aaron' OR id ='1.0'

我们在前面的查询中使用 EXPLAIN ANALYZE,如下所示:

EXPLAIN ANALYZE 
SELECT * 
FROM 
"MYSQL"."sportsdata"."player_partitioned" 
WHERE first_name = 'Aaron' OR id ='1.0'

与 LIKE 语句类似,以下输出显示查询扫描了表,而不是仅下推到与 WHERE 子句匹配的记录。此查询仅输出 16 条记录,但数据扫描表示已完成扫描。

适用于 DynamoDB 的 Athena 连接器的下推优化

对于使用 DynamoDB 连接器的示例,我们使用以下数据:

行数 以 KB 为单位的大小
sportseventinfo 657 85.75

我们使用以下查询测试 DynamoDB 表的筛选谓词和项目筛选操作。此查询尝试获取给定地点的所有活动和体育赛事。我们使用 EXPLAIN ANALYZE 进行查询,如下所示:

EXPLAIN ANALYZE 
SELECT EventId,
Sport 
FROM "DDB"."default"."sportseventinfo" 
WHERE Location = 'Chase Field'

EXPLAIN ANALYZE 的输出显示,筛选谓词仅检索了 21 条记录,而项目筛选只选择了两列下推到源。因此,此查询的数据扫描小于表大小。

现在我们看看筛选谓词下推在哪里不起作用。在 WHERE 子句中,如果您将 TRIM() 函数应用于 Location 列然后进行筛选,谓词下推优化不适用,但我们仍会看到投影筛选优化,它确实适用。请参阅以下代码:

EXPLAIN ANALYZE 
SELECT EventId,
Sport 
FROM "DDB"."default"."sportseventinfo" 
WHERE trim(Location) = 'Chase Field'

此查询的 EXPLAIN ANALYZE 输出显示,该查询会扫描所有行,但仍仅限于两列,这表明在应用 TRIM 函数时,筛选谓词不起作用。

我们从前面的示例中看到,Amazon RDS for MySQL 的 Athena 数据来源连接器确实支持用于下推优化的筛选谓词和投影谓词,但我们也看到,在筛选谓词中使用时,LIKE、OR 和 TRIM 等操作不支持下推到源。因此,如果您在联合 Athena 查询中遇到无法解释的费用,我们建议将 EXPLAIN ANALYZE 与查询结合使用,并确定您的 Athena 连接器是否支持下推操作。

请注意,运行 EXPLAIN ANALYZE 会产生成本,因为它会扫描数据。

结论

在这篇博文中,我们展示了如何使用 EXPLAIN 和 EXPLAIN ANALYZE 来分析 AWS S3 上数据来源的 Athena SQL 查询,以及 DynamoDB 和 Amazon RDS for MySQL 等数据来源的 Athena 联合 SQL 查询。您可以以此为例来优化查询,这也将节省成本。


关于作者

Nishchai JM 是 Amazon Web Services 的分析专家解决方案架构师。他擅长构建大数据应用程序,并帮助客户在云端实现应用程序的现代化。他认为数据是新石油,大部分时间都花在从数据中获取见解上。

 

 

 

 

Varad Ram 是 Amazon Web Services 的高级解决方案架构师。他喜欢帮助客户采用云技术,对人工智能特别感兴趣。他认为深度学习将推动未来的技术发展。在业余时间,他喜欢与女儿和儿子一起去户外。