这是我参与「第四届青训营 -大数据场」笔记创作活动的的第1篇笔记
本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
第一节:SQL 查询优化器浅析
大数据体系和SQL
SQL的处理流程
Parser
将字符串转化为AST
词法分析与语法分析
(待补充)
AST(抽象语法树)
抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。抽象语法树并不依赖于源语言的语法,也就是说语法分析阶段所采用的上下文无文文法,因为在写文法时,经常会对文法进行等价的转换(消除左递归,回溯,二义性等),这样会给文法分析引入一些多余的成分,对后续阶段造成不利影响,甚至会使合个阶段变得混乱。因些,很多编译器经常要独立地构造语法分析树,为前端,后端建立一个清晰的接口。
抽象语法树在很多领域有广泛的应用,比如浏览器,智能编辑器,编译器。
实现
递归下降(ClickHouse)、Flex和Bison(PostgreSQL)、javaCC(Flink)、Antlr(Presto,Spark)
Analyzer和Logical Plan
Analyzer:
- 检查并绑定Database,Table,Column等元信息
- SQL的合法性检查,比如min/max/avg的输入是数值
- AST转化为Logical Plan
Logical Plan:
- 逻辑地描述SQL对应的分步骤计算操作
- 计算操作:算子(operator)
举个例子:
以上是实际SQL语句执行过程
Optimizer
Executor
Physical Plan:
Plan Fragment:(执行计划子树)
- 目标:最小化网络数据传输
- 利用上数据的物理分布(数据亲和性)
- 增加Shuffle算子
Executor:
- 单机并行:cache,pipeline,SIMD
- 多级并行:一个fragment对应多个实例
拆分执行计划时,每个节点之间通过增加Sheffle算子连接。
拆分执行计划成多个Plan Fragment然后发送给节点执行,每个节点增加一个Executor。
批量计算与流式计算
大数据的计算模式主要分为批量计算(batch computing)、流式计算(stream computing)、交互计算(interactive computing)、图计算(graph computing)等。其中,流式计算和批量计算是两种主要的大数据计算模式,分别适用于不同的大数据应用场景。
流数据(或数据流)是指在时间分布和数量上无限的一系列动态数据集合体,数据的价值随着时间的流逝而降低,因此必须实时计算给出秒级响应。流式计算,顾名思义,就是对数据流进行处理,是实时计算。批量计算则统一收集数据,存储到数据库中,然后对数据进行批量处理的数据计算方式。主要体现在以下几个方面:
1、数据时效性不同:流式计算实时、低延迟, 批量计算非实时、高延迟。
2、数据特征不同:流式计算的数据一般是动态的、没有边界的,而批处理的数据一般则是静态数据。
3、应用场景不同:流式计算应用在实时场景,时效性要求比较高的场景,如实时推荐、业务监控...批量计算一般说批处理,应用在实时性要求不高、离线计算的场景下,数据分析、离线报表等。
4、运行方式不同,流式计算的任务持续进行的,批量计算的任务则一次性完成。
SQL执行计划
SQL 执行的基本流程
一般业务:客户端(也就是我们的业务代码)发送了一段SQL文本,服务端接收到了一段SQL文本然后进行解析处理,最终返回一段文本(执行结果)。
-
连接处理:
该模块主要是管理客户端的连接,客户端可以通过TCP/IP、命名管道、共享内存、套接字等方式与服务端进行连接,服务端接收到连接后,会专门生成一个线程去处理客户端的请求。当完成客户端的请求后,该线程不会被销毁,而是放入线程池中,从而减少了频繁创建和删除线程的消耗,大大节省了系统资源和提高了效率。
客户端每次发起连接请求时,都会携带用户名、密码等验证信息,如果服务器验证不通过,则会拒绝连接,同时,如果很多客户端同时请求连接,为了避免服务端程序崩溃和提高效率,可以限制最大的连接数量。
-
解析和优化:
- 查询缓存:为了提高相应效率,MYSQL服务端程序会根据客户端请求的信息生成对应的缓存,如果请求的信息符合缓存中的,则直接返回,无需再去与底层进行更多的交互
- 语法解析
- 查询优化
-
存储引擎
常见查询优化器
2种分类方法:
-
第一种,按照遍历计划树划分:
- Top-down Optimizer:从目标输出开始,由上往下遍历计划树,找到完整的最优执行计划
- Bottom-up Optimizer:从0开始,由下往上遍历计划树,找到完整的执行计划
-
第二种,按照优化规则划分:
- Rule-based Optimizer(RBO):根据关系代数等价语义,重写查询;基于启发式规则;会访问表的元信息(catalog),不会涉及具体的表数据(data)
- Cost-based Optimizer(CBO):使用一个模型估算执行计划的代价,选择代价最小的执行计划
RBO
关系代数
优化规则
- 优化I/O
- 优化网络
- 优化CPU&Memeory
列裁剪
select pv.siteId,user.name
from pv join user
on pv.siteId = user.siteId and pv.userId = user.id
where user.siteId > 123
优化部分:
只需要SCAN查询所需要的列即可,而不是整张表
谓词下推
select pv.siteId,user.name
from pv join user
on pv.siteId = user.siteId and pv.userId = user.id
where user.siteId > 123
优化部分:
尽早地过滤条件,FILTER user.siteId > 123 早于 JION pv.siteId = user.siteId and pv.userId = user.id
传递闭包
select pv.siteId,user.name
from pv join user
on pv.siteId = user.siteId and pv.userId = user.id
where user.siteId > 123
优化部分:
FILTER pv.siteId > 123 被推导出来,创建一个新的FILTER
Runtime Filter
select pv.siteId,user.name
from pv join user
on pv.siteId = user.siteId and pv.userId = user.id
where user.siteId > 123
优化部分:
- min-max:
- in-list:
- bloom filter:
CBO
也即“基于代价的优化器”,该优化器通过根据优化规则对关系表达式进行转换,生成多个执行计划,然后CBO会通过根据统计信息(Statistics)和代价模型(Cost Model)计算各种可能“执行计划”的“代价”,即COST,从中选用COST最低的执行方案,作为实际运行方案。
- 执行计划地代价等于所有算子地执行代价之和
- 通过RBO得到(所有)的等价执行计划
算子代价:CPU、内存、磁盘I/O、网络I/O等代价
统计信息
-
原始统计信息:
- 表或者分区级别:行数、行平均大小、表在磁盘中占用了多少字节等
- 列级别:min、max、num nulls、num not nulls、num distinct value(NDV)、histogram等
-
推导统计信息:
- 选择率:(selectivity),对于某一个过滤条件,查询会从表中返回多大比例的数据
- 基数:(cardinality),在查询计划中常指算子需要处理的行数
统计信息的收集方式
-
在DDL里指定需要收集的统计信息,数据库会在数据写入时收集或者更新统计信息
create table test( ... ) properties("stats_columns" = "r_name"); -
手动执行explain analzye statement,触发数据库收集或者更新统计信息
analzye table test compute statistics for columns col1,col2,... -
动态采样
select count(*) from test
统计信息推导规则
假设列和列之间是独立的,列的信息均匀分布
Filter Selectivity:AND、OR、 NOT、 =、 <
执行计划枚举
通常使用贪心算法或者动态规划选出最优的执行计划
- 动态规划
(待完成)
Apache Calcite
支持异构数据模型:
- 关系型
- 半结构化
- 流式
- 地理空间数据
内置RBO和CBO
HepPlanner
优化规则
模式:匹配表达式子树
等价变换:得到新的表达式
内置有100+优化规则
匹配规则
- ARBIRARY/DEPTH_FIRST:深度优先
- TOP_DOWN:拓扑排序
- BOTTOM_UP:与TOP_DOWN相反
VolcanoPlanner
基于Volcano/Cascade框架
Memo:存储候选执行计划