这是我参与「第四届青训营 」笔记创作活动的第1天
SQL Optimizer 解析
SQL处理流程
在了解SQL优化器之前,我们首先要知道数据库执行SQL语句的处理流程:
- 首先SQL语句进入解析器,被解析成 抽象语法树(Abstract Syntax Tree) 结构
- 分析器 将AST转化为 逻辑计划(Logic Plan)
- 逻辑计划经优化器输出为 物理计划(Physical Plan) 交由 执行器 执行
解析器 Parser
涉及下面两个阶段
词法分析: 拆分字符串,提取关键字,字符串,数值等
语法分析: 把词条按照定义的语法规则组装成抽象语法树结构
分析器 Analyzer
访问库/表元信息并绑定 和判断SQL是否合理,当然最主要的是将 AST 转换成逻辑计划树
优化器 Optimizer
优化器可以说是数据库最复杂的模块,也是最重要的模块
一般 SQL 越复杂,需要连接的表就越多(比如在分布式的环境中),查询优化的意义就越大,因为不同执行方式的性能差别可能有成百上千倍
执行器 Executor
按照物理执行计划扫描和处理数据,充分利用机器资源(CPU 流水线,乱序执行,cache,SIMD)
逻辑计划树
简的来说,逻辑计算树是将SQL如何执行查询和计算的分步骤逻辑的(并没有明确实际的算法)用树来表示。
其中树的每一个节点代表一个算子,每一条边代表数据的流动方向
物理计划树
在单机 Plan 的基础上最小化数据移动和最大化本地 Scan,生成 PlanFragment 树就是分布式物理计划的目标
每个 PlanFragment 可以在每个 executor 节点生成 1 个或多个执行实例,不同执行实例处理不同的数据集,通过并发来提升查询性能
查询优化
常见的查询优化器分为两种 RBO(Rule-based Optimizer) 和 CBO(Cost-based Optimizer),即基于规则和基于代价的优化器。
查询的优化大多用到关系代数的等价交换(结合、交换、传递)
RBO(Rule-based Optimizer)
优化目标
- IO
- NetWork
- CPU、Memory
基于规则的优化,是基于很多已有的经验,一般无法实现最优的优化
优化方法
现在用一个查询语句演示一下常见的RBO方法
SELECT t1.id,t2.name
FROM t1 JOIN t2
ON t1.id = t2.id AND t1.uid = t2.uid
WHERE t2.id > 99;
上面这幅图显示初始的查询方案,先扫描两张表,然后连接、过滤、投影依次进行,显然这并不是一个很好的方案,我们后面需要对其进行一些简单的优化。
列裁剪:
列裁剪的方式主要是在扫描这一步,将扫描整张表的操作,替换成 只扫描所需要的属性 能够很大的减少开销,特别是对于大数据环境下的查询。
谓词下推:
谓词下推简的来说就是 将过滤操作Filter下推到靠近数据源的地方,让无用数据能尽早被过滤掉。
本例就可以将Filter操作移动到Join之前。
传递闭包:
传递闭包的原理是通过已有的条件,无中生有新的过滤条件来过滤数据。
如示例查询中可以通过t1.id = t2.id 和 t2.id >99能够推出新的过滤条件t1.id>99
Runtime Filter
Runtime Filter旨在为某些 Join 查询 在运行时动态生成过滤条件,来减少扫描的数据量,避免不必要的I/O和网络传输,从而加速查询。
运行时过滤采用的方法有很多这里简单说3个
min-max: 可以理解为使用范围过滤,但这种过滤要求查询的数据紧密,若数据分布松散导致范围仍然很大,这个过滤的意义就不大了。
in-list: 在构建Runtime Filter的一方值较少的时候可以采用in-list的方式,过滤的方式自然为扫描数据是否在list中。
bloom filter: 这种过滤方式时一种二进制向量数据结构,它具有空间和时间效率,被用来检测一个元素是不是集合中的一个成员。
CBO(Cost-based Optimizer)
执行计划预估代价,选择代价最小的方案
需要考虑的算子代价由:CPU、内存、磁盘IO、网络IO等
值得注意的是不同的系统计算代价的算法各异
graph LR;
a(统计信息 + 推导规则)--> b(计算算子代价) --> c(计算执行计划代价) --> d(执行计划枚举)
统计信息
- 原始表统计信息
- 表\分区
- 列
- 推导统计信息
- 选择率:返回数据的比例
- 基数:需要处理的行数
统计信息的推导
要求列与列独立,列值均匀分布,有点类似概率的计算
-
AND条件: fs(a AND b)=fs(a) * fs(b)
-
OR条件: fs(a OR b)=fs(a) + fs(b) - fs(a) * fs(b)
-
NOT条件: fs(NOT a)=1 - fs(a)
-
等于条件
- 若值不在[min,max]范围内:0
- 若值在[min,max]范围内:1/NDV
-
小于条件
- value<min:0
- value>max:1
- min<value<max:(value-min)/(max-min)
但是这样的推动与实际并不相符合,因为现实生活中的数据不一定符合均匀分布。
执行计划枚举
通常是采用贪心算法或者动态规划选出最优的执行计划