AnalyticDB MySQL--读懂执行计划

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
阿里云百炼推荐规格 ADB PostgreSQL,4核16GB 100GB 1个月
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 本文是AnalyticDB MySQL的学习笔记,描述了sql的执行过程,执行计划的基本概念,以及怎样解释执行计划

1 AnalyticDB MySQL中sql语句的执行过程

      AnalyticDB MySQL采用三层架构,分为接入层、计算层和存储层,这个分层和MySQL等数据库也是基本一致的,由于AnalyticDB MySQL是用于数据仓库的分析型数据库,其底层的实现同MySQL完全不同。

     在AnalyticDB MySQ,这三本层次都是分布式的,接入层主要负责协议层接入、SQL解析和优化、实时写入Sharding、数据调度和查询调度,是sql执行计划中的控制节点;计算层是阿里的羲和分析计算引擎,具备分布式MPP和DAG融合执行能力,结合智能

优化器,可支持高并发和复杂SQL混合负载,存储层是阿里的玄武分析存储引擎,基于Raft协议实现的分布式实时强一致高可用存储引擎,通过数据分片和Multi-Raft实现并行。阿里官网的架构图如下:

整体架构.png

      对应这三个层次,在AnalyticDB MySQ中,一条语句的执行流程是这样的:

      1 客户端将SQL语句提交到AnalyticDB MySQL版的前端接入节点(即Controller节点)。Controller节点中解析器(Parser)会对SQL语句进行解析优化并生成最终的逻辑执行计划(Plan),并根据数据是否需要在网络间传输来决定是否需要对计划进行切分成多个stage。逻辑执行计划中也会规定特定的执行处理方式,例如Join类型、Join顺序、聚合方式以及数据重分布方式等。

       执行计划任务的节点(即Executor节点)会接收最终的逻辑执行计划并将其转化成物理执行计划。物理执行计划由Stage和算子(Operator)组成,在计算层进行执行,stage在每一个计算节点上的执行被称为任务。任务可以有一个或多个算子组成。任务的执行中,计算节点从存储节点获取数据,也可能会将过滤等操作下推到存储节点执行。

       Executor节点将数据处理的最终结果返回到客户端,或者写入AnalyticDB MySQL版集群的内部表以及其它外部存储系统(如OSS)中。

2 AnalyticDB MySQL 执行计划的基本概念

       AnalyticDB MySQL中的语句大多是并行执行的,其执行计划具有分布式和并行执行的特点,执行计划中有几个基本概念也和MySQL不同,读懂执行计划的前提是对这些基本概念要清楚,主要有stage、Task和算子三个基本概念。

     Stage(执行阶段),AnalyticDB MySQL版中的查询会首先被切分为多个Stage来执行,一个Stage就是执行计划中某一部分的物理实体。Stage的数据来源可以是底层存储系统中的数据或者网络中传输的数据,一个Stage由分布在不同Executor节点上相同类型的Task组成,多个Task会并行处理数据。

     Task是一个Stage在某个Executor节点上的执行实体,多个同类型的Task组成一个Stage,在集群内部并行处理数据。

     算子(Operator)

算子是AnalyticDB MySQL版的基本数据处理单元。AnalyticDB MySQL版会根据算子所表达的语义或算子间的依赖关系,决定使用并行还是串行执行来处理数据。

     这三个概念的之间的关系如下图所示:

基本概念关系图.png


3 AnalyticDB MySQL中stage

    stage是执行计划的重要阶段,通过一个具体的stage的例子来看一下就明白了。

Stage层计划执行树.png

      上面的是来源是阿里官网,是AnalyticDB MySQL版的SQL诊断功能以树形图的形式展现SQL查询的执行计划的stage层执行计划树。stage是下网上执行的,先由具有扫描算子的Stage进行数据扫描,再经过中间Stage节点的层层处理后,再由最上层的根节点将查询结果返回客户端。

      数据由下层流向上层的有三种方式,如果不理解这三种方式,就很难理解执行计划。这三种方式是广播(broadcast),重分区(repartition),和汇集(gather)。看一下官网的三个图,对这三种方式就一目了然了。

      图里的上游下游是时间上的概念,而不是空间或层次上的概念,数据总是从上游流转到下游。

3.1 广播

       上游的表被复制到下游的每个节点

数据输出方法广播.png

3.2 重分区

     上游的分区分布和下游要求的分区分布方式不同,根据下游的要求重新在每个节点上分布分区。

数据输出方法重分区.png


3.3 汇集

     上游的数据被汇集到一个下游节点上进行汇总计算。

数据输出方法-gather.png

4 算子

     执行计划中的任务(task)不必多做解释,一个阶段(stage)在一个节点上的执行就是任务。

      执行计划里的算子因为涉及到分布式和并行,出现了几个在MySQL中没有的算子,需要说明一下。

4.1 分布式和并行相关算子

      RemoteSource

      该算子用来表示当前Stage的输入数据是通过网络从远程节点传输过来的。

      RemoteExchange

      该算子用来表示上游向下游Stage传输数据时所用的方法。上下游Stage间传输数据的方法有如下几种:

       A)Broadcast:表示上游Stage中每个计算节点的数据都会复制到所有下游Stage的计算节点。

       B)Repartition:表示上游Stage中每个节点的数据会按照固定的规则切分后,再分发到下游Stage的指定计算节点。

       C)Gather:表示上游Stage中每个节点的数据会集中到下游Stage中某一个特定的计算节点。

      可以看出来,RemoteExchange三个方法和stage间传递数据的三种方式相对应。

4.2 其它算子

     其它算子比较好理解,同MySQL数据库执行计划中的操作基本类似,如Aggregation算子通过sum()、count()、avg()等函数对数据进行聚合或分组聚合操作DistinctLimit算子对应SQL语句中的DISTINCT LIMIT,MarkDistinct

对应SQL语句中的count(DISTINCT)操作,Project对应SQL语句中对特定字段的投影操作。

      有一个算子也是Analytic DB MySQL特有的,这个就是StageOutput算子,

这个算子用于将当前Stage处理后的数据通过网络传输到下游Stage的节点。

4.3   算子的执行

算子怎样组成任务可以从下面图中看出来。

算子执行计划.png

5 解释和分析执行计划

     有了上面的准备,我们可以解释和分析AnalyticDB MySQL的执行计划了,下面这个例子来自官网。要分析的sql语句先列出来。

SELECTcount(*)FROM      nation, region, customer
WHERE      c_nationkey = n_nationkey
AND n_regionkey = r_regionkey
AND r_name ='ASIA';

     这条sql语句并不复杂,是一个三个表的内连接,获得区域为亚洲的用户的数量,限制条件加载region表上。这条语句的执行计划看起来要复杂一些

Output[count(*)]      │   Outputs:[count:bigint]      │   Estimates:{rows:1(8B)}count(*):=count      └─ Aggregate(FINAL)         │   Outputs:[count:bigint]         │   Estimates:{rows:1(8B)}count:=count(`count_1`)         └─ LocalExchange[SINGLE]()            │   Outputs:[count_0_1:bigint]            │   Estimates:{rows:1(8B)}            └─ RemoteExchange[GATHER]               │   Outputs:[count_0_2:bigint]               │   Estimates:{rows:1(8B)}               └─ Aggregate(PARTIAL)                  │   Outputs:[count_0_4:bigint]                  │   Estimates:{rows:1(8B)}                  │   count_4 :=count(*)                  └─ InnerJoin[(`c_nationkey` = `n_nationkey`)][$hashvalue, $hashvalue_0_6]                     │   Outputs:[]                     │   Estimates:{rows:302035(4.61MB)}                     │   Distribution: REPLICATED
                     ├─ Project[]                     │  │   Outputs:[c_nationkey:integer, $hashvalue:bigint]                     │  │   Estimates:{rows:1500000(5.72MB)}                     │  │   $hashvalue := `combine_hash`(BIGINT'0', COALESCE(`$operator$hash_code`(`c_nationkey`),0))                     │  └─ RuntimeFilter
                     │     │   Outputs:[c_nationkey:integer]                     │     │   Estimates:{rows:1500000(5.72MB)}                     │     ├─ TableScan[adb:AdbTableHandle{schema=tpch, tableName=customer, partitionColumnHandles=[c_custkey]}]                     │     │      Outputs:[c_nationkey:integer]                     │     │      Estimates:{rows:1500000(5.72MB)}                     │     │      c_nationkey := AdbColumnHandle{columnName=c_nationkey, type=4, isIndexed=true}                     │     └─ RuntimeCollect
                     │        │   Outputs:[n_nationkey:integer]                     │        │   Estimates:{rows:5(60B)}                     │        └─ LocalExchange[ROUND_ROBIN]()                     │           │   Outputs:[n_nationkey:integer]                     │           │   Estimates:{rows:5(60B)}                     │           └─ RuntimeScan
                     │                  Outputs:[n_nationkey:integer]                     │                  Estimates:{rows:5(60B)}                     └─ LocalExchange[HASH][$hashvalue_0_6]("n_nationkey")                        │   Outputs:[n_nationkey:integer, $hashvalue_0_6:bigint]                        │   Estimates:{rows:5(60B)}                        └─ Project[]                           │   Outputs:[n_nationkey:integer, $hashvalue_0_10:bigint]                           │   Estimates:{rows:5(60B)}                           │   $hashvalue_10 := `combine_hash`(BIGINT'0', COALESCE(`$operator$hash_code`(`n_nationkey`),0))                           └─ RemoteExchange[REPLICATE]                              │   Outputs:[n_nationkey:integer]                              │   Estimates:{rows:5(60B)}                              └─ InnerJoin[(`n_regionkey` = `r_regionkey`)][$hashvalue_0_7, $hashvalue_0_8]                                 │   Outputs:[n_nationkey:integer]                                 │   Estimates:{rows:5(60B)}                                 │   Distribution: REPLICATED
                                 ├─ Project[]                                 │  │   Outputs:[n_nationkey:integer, n_regionkey:integer, $hashvalue_0_7:bigint]                                 │  │   Estimates:{rows:25(200B)}                                 │  │   $hashvalue_7 := `combine_hash`(BIGINT'0', COALESCE(`$operator$hash_code`(`n_regionkey`),0))                                 │  └─ RuntimeFilter
                                 │     │   Outputs:[n_nationkey:integer, n_regionkey:integer]                                 │     │   Estimates:{rows:25(200B)}                                 │     ├─ TableScan[adb:AdbTableHandle{schema=tpch, tableName=nation, partitionColumnHandles=[]}]                                 │     │      Outputs:[n_nationkey:integer, n_regionkey:integer]                                 │     │      Estimates:{rows:25(200B)}                                 │     │      n_nationkey := AdbColumnHandle{columnName=n_nationkey, type=4, isIndexed=true}                                 │     │      n_regionkey := AdbColumnHandle{columnName=n_regionkey, type=4, isIndexed=true}                                 │     └─ RuntimeCollect
                                 │        │   Outputs:[r_regionkey:integer]                                 │        │   Estimates:{rows:1(4B)}                                 │        └─ LocalExchange[ROUND_ROBIN]()                                 │           │   Outputs:[r_regionkey:integer]                                 │           │   Estimates:{rows:1(4B)}                                 │           └─ RuntimeScan
                                 │                  Outputs:[r_regionkey:integer]                                 │                  Estimates:{rows:1(4B)}                                 └─ LocalExchange[HASH][$hashvalue_0_8]("r_regionkey")                                    │   Outputs:[r_regionkey:integer, $hashvalue_0_8:bigint]                                    │   Estimates:{rows:1(4B)}                                    └─ ScanProject[table= adb:AdbTableHandle{schema=tpch, tableName=region, partitionColumnHandles=[]}]                                           Outputs:[r_regionkey:integer, $hashvalue_0_9:bigint]                                           Estimates:{rows:1(4B)}/{rows:1(B)}                                           $hashvalue_9 := `combine_hash`(BIGINT'0', COALESCE(`$operator$hash_code`(`r_regionkey`),0))                                           r_regionkey := AdbColumnHandle{columnName=r_regionkey, type=4, isIndexed=true}


      执行计划的localExchange是在本地节点进行的对数据的操作,RuntimeCollect是从前面的算子获得的数据。      

      执行计划从下往上看,最下面一个算子是ScanProject,扫描的是region表,扫描后的数据由LocalExchange[HASH]算子进行哈希计算,然后和nation表进行哈希join,

join的键是n_regionkey和r_regionkey,join的输出是n_nationkey,join操作的执行计划看起来比较复杂,可以看到对nation表的TableScan操作,以及执行中获得的对region表哈希操作后的数据,这个数据只有一行。这个join的结果再和用户表进行join后再每个节点上进行部分聚合(Aggregate(PARTIAL),最后汇集(RemoteExchange[GATHER])到一个节点上进行最后聚合(Aggregate(FINAL)),然后输出(Output[count(*)])。


   




相关实践学习
AnalyticDB MySQL海量数据秒级分析体验
快速上手AnalyticDB MySQL,玩转SQL开发等功能!本教程介绍如何在AnalyticDB MySQL中,一键加载内置数据集,并基于自动生成的查询脚本,运行复杂查询语句,秒级生成查询结果。
阿里云云原生数据仓库AnalyticDB MySQL版 使用教程
云原生数据仓库AnalyticDB MySQL版是一种支持高并发低延时查询的新一代云原生数据仓库,高度兼容MySQL协议以及SQL:92、SQL:99、SQL:2003标准,可以对海量数据进行即时的多维分析透视和业务探索,快速构建企业云上数据仓库。 了解产品 https://www.aliyun.com/product/ApsaraDB/ads
相关文章
|
20天前
|
缓存 关系型数据库 MySQL
MySQL执行计划选择策略:揭秘查询优化的艺术
【10月更文挑战第15天】 在数据库性能优化中,选择最优的执行计划是提升查询效率的关键。MySQL作为一个强大的关系型数据库管理系统,提供了复杂的查询优化器来生成执行计划。本文将深入探讨如何选择合适的执行计划,以及为什么某些计划更优。
43 2
|
20天前
|
缓存 关系型数据库 MySQL
MySQL执行计划深度解析:如何做出最优选择
【10月更文挑战第23天】 在数据库查询性能优化中,执行计划的选择至关重要。MySQL通过查询优化器来生成执行计划,但有时不同的执行计划会导致性能差异。理解如何选择合适的执行计划,以及为什么某些计划更优,对于数据库管理员和开发者来说是一项必备技能。
30 2
|
20天前
|
SQL 关系型数据库 MySQL
美团面试:Mysql如何选择最优 执行计划,为什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴面试美团时遇到了关于MySQL执行计划的面试题:“MySQL如何选择最优执行计划,为什么?”由于缺乏系统化的准备,小伙伴未能给出满意的答案,面试失败。为此,尼恩为大家系统化地梳理了MySQL执行计划的相关知识,帮助大家提升技术水平,展示“技术肌肉”,让面试官“爱到不能自已”。相关内容已收录进《尼恩Java面试宝典PDF》V175版本,供大家参考学习。
|
5月前
|
分布式计算 DataWorks 关系型数据库
实时数仓 Hologres产品使用合集之如何将MySQL数据初始化到分区表中
实时数仓Hologres的基本概念和特点:1.一站式实时数仓引擎:Hologres集成了数据仓库、在线分析处理(OLAP)和在线服务(Serving)能力于一体,适合实时数据分析和决策支持场景。2.兼容PostgreSQL协议:Hologres支持标准SQL(兼容PostgreSQL协议和语法),使得迁移和集成变得简单。3.海量数据处理能力:能够处理PB级数据的多维分析和即席查询,支持高并发低延迟查询。4.实时性:支持数据的实时写入、实时更新和实时分析,满足对数据新鲜度要求高的业务场景。5.与大数据生态集成:与MaxCompute、Flink、DataWorks等阿里云产品深度融合,提供离在线
|
2月前
|
SQL 存储 关系型数据库
深入 MySQL 的执行计划与性能优化
深入 MySQL 的执行计划与性能优化
39 0
|
3月前
|
SQL 关系型数据库 MySQL
实时数仓 Hologres操作报错合集之Flink CTAS Source(Mysql) 表字段从可空改为非空的原因是什么
实时数仓Hologres是阿里云推出的一款高性能、实时分析的数据库服务,专为大数据分析和复杂查询场景设计。使用Hologres,企业能够打破传统数据仓库的延迟瓶颈,实现数据到决策的无缝衔接,加速业务创新和响应速度。以下是Hologres产品的一些典型使用场景合集。
|
3月前
|
存储 SQL 人工智能
AnalyticDB for MySQL:AI时代实时数据分析的最佳选择
阿里云云原生数据仓库AnalyticDB MySQL(ADB-M)与被OpenAI收购的实时分析数据库Rockset对比,两者在架构设计上有诸多相似点,例如存算分离、实时写入等,但ADB-M在多个方面展现出了更为成熟和先进的特性。ADB-M支持更丰富的弹性能力、强一致实时数据读写、全面的索引类型、高吞吐写入、完备的DML和Online DDL操作、智能的数据生命周期管理。在向量检索与分析上,ADB-M提供更高检索精度。ADB-M设计原理包括分布式表、基于Raft协议的同步层、支持DML和DDL的引擎层、高性能低成本的持久化层,这些共同确保了ADB-M在AI时代作为实时数据仓库的高性能与高性价比
|
5月前
|
Cloud Native 关系型数据库 MySQL
云原生数据仓库AnalyticDB产品使用合集之如何修改云ADB MySQL版的默认LIMIT
阿里云AnalyticDB提供了全面的数据导入、查询分析、数据管理、运维监控等功能,并通过扩展功能支持与AI平台集成、跨地域复制与联邦查询等高级应用场景,为企业构建实时、高效、可扩展的数据仓库解决方案。以下是对AnalyticDB产品使用合集的概述,包括数据导入、查询分析、数据管理、运维监控、扩展功能等方面。
93 21
|
5月前
|
Cloud Native 关系型数据库 MySQL
《阿里云产品四月刊》—云原生数据仓库 AnalyticDB MySQL 版 新功能
阿里云瑶池数据库云原生化和一体化产品能力升级,多款产品更新迭代
101 3
|
5月前
|
SQL 关系型数据库 MySQL
【Mysql】 深入理解MySQL的执行计划
【Mysql】 深入理解MySQL的执行计划
248 4