优化器概念
优化器是数据库中用于把关系表达式转换成执行计划的核心组件,很大程度上决定了一个系统的性能
优化器会包含一系列优化规则,这些优化规则可以对关系表达式进行等价转换,从而生成执行计划
优化规则常见逻辑算子
DataSource:数据源,也就是我们SQL语句中的表,select name from table1中的table1Selection:选择,Where条件,如select name from table1 where id = 1中的where后的过滤条件Projection:投影,指搜索选择的列,如select name from table1 where id = 1中的列nameJoin:连接,如select * from table1 table2 where table1.name=table2.name就是把两个表做Join,连接条件是最简单的等值连接,当然还有其他我们熟知的inner join,left join,right join等等Sort:排序,如select * from table1 order by id里面的order by,无序的数据通过这个算子处理后,输出有序的数据Aggregation:分组,如select sum(score) from table1 group by name中的group by,按照某些列进行分组,分组后可以进行一些聚合操作,比如Max、Min、Sum、Count、Average等等Apply:子查询,如select * from (select id,name from table1) as t中的(select id,name from table1) as t,可以进行嵌套查询
优化规则-谓词下推
将外层查询块where子句中的谓词移入所包含的较低层次的查询块,从而能够提前进行数据过滤以及更好的使用索引
举例
比如对于表t1(100条数据),t2(100条数据),对于查询语句select * from t1,t2 where t1.a > 3 and t2.b >5
执行
- 直接执行:执行时候是把
t1和t2两个表做笛卡尔积,需要处理10000条数据,然后再根据条件进行过滤 - 进行谓词下推:比如
t1.a > 3的数据有10条,t2.b > 5的有5条,先进行过滤我们所需要处理的数据条数则只有50条了,这就是尽量把过滤条件往下推到子节点上,就可以避免访问很多数据,从而达到优化的效果
对于算子的谓词下推
DataSource算子,直接将过滤条件推给各个DataSource算子即可- 对于
Join算子,则会首先进行简化,将外连接转化为内连接,收集连接条件,区分出哪些来自于Join的左节点哪些来自于Join的右节点,分别像左右节点进行下推
注意点
不能下推Limit,因为先进行Limit n再做Selection操作和先做Selection操作再Limit n得到的结果是不一样的
优化规则-列裁剪
对于没用到的列,则没有必要读取它们的数据去浪费无谓的IO
举例
比如我们有一张表table1,它含有四列数据a,b,c,d
当我们执行查询select a from table1 where c >10时,table1中只有a,c两列被用到了
Selection算子用到c列Projection算子用到a列
那么DataSource读取数据时,b,d两列则不需要读取,可以裁剪掉
总结
列裁剪的算法就是自顶向下的把算子过一遍,某个节点需要用到的列就等于它自己需要用到的列加上它的父节点所需要用到的列,这样得到整个SQL语句所涉及到的列,从而再读取数据时只读取需要的列即可
优化规则-常量折叠
在编译优化时,多个变量进行计算时,而且能够直接计算出结果,那么变量将由常量直接替换
举例
比如select * from table1 where a > 3*5会转换为select * from table1 where a > 15
优化规则-常量传播
常量传播,在编译优化时,将能够计算出结果的变量替换为常量
实现逻辑
依赖一种叫做到达定值(reaching definition)的前向数据流分析(forward data-flow analysis),要确定某个定值能被传播到哪些使用点,或者反过来说,某个使用点上应该采用哪个版本的定值,如果在某个使用点上发现应该使用的定值是一个常量的话,就可以在此处做诸如常量折叠之类的常量优化了
举例
select * from table1 where a > 5 and a < 4一看就能看出来,不存在
a > 5 && a < 4的值,但是未经优化的SQL会进行全表扫描查询a > 5和a < 4,这个时候就需要sql优化语句,从而消除无用的节点,判断是否存在结果select * from table1 where a = b and b = 3可以优化为
select * from table1 where a = 3 and b = 3
优化规则-投影消除
投影消除是把不必要的Projection给消除掉
如果Projection算子需要投影的列跟子节点的输出列一样,那么这个投影就是一个废操作,可以被消除掉
举例
select a,b from table1如果再表table1中刚好只有a,b两列,也就是DataSource的输出和Projection需要投影的列一样,那么这时候就没必要在TableScan之后再做一次Projection操作了select a from (select a,b,c from table2)这条语句里面有两个Projection,分别是最上层的Projection(a)和它的子节点Projection(a,b,c)那么Projection(a,b,c)这个节点就是废操作,可以被消除掉如果Projection的子节点还是Projection的话,那么子节点的Projection就没有意义了,可以干掉Aggregation在某种意义上也属于投影操作,因为从这个节点出来的都是列的概念,比如Max(a)、Min(b)等,因此在Aggregation->Projection的过程中,这个Projection也是可以被消除掉的
优化规则-最大最小消除
最大最小消除严格上说不是标准逻辑优化里面需要做的事情
举例
最小消除:
select min(a) from table1生成的逻辑执行计划是一个TableScan上面接一个Aggregation,也就是说这是一个全表扫描的操作可以转换为
select a from table1 order by a desc limit 1生成的逻辑执行计划是TableScan + Sort + Limit,在某些情况,比如a是主键或者是存在索引,数据本身是有序的,Sort就可以消除,最终变成TableScan或者IndexLookUp加Limit,这样子就不需要全表扫了,读到第一条数据就得到结果最大消除
select max(id) from table1优化为select max(id) from (select id from table1 order by id desc limit 1 where id is not null) t最小消除:
select min(id) from table1优化为select min(id) from (select id from table1 order by id limit 1 where id is not null) table1
查询优化器分类
基于规则的优化器(Rule-Based Optimizer,RBO)
根据优化规则对关系表达式进行转换,一个关系表达式经过优化规则后会变成另外一个关系表达式,同时原有表达式会被裁剪掉,经过一系列转换后生成最终的执行计划
RBO中包含了一套有着严格顺序的优化规则,同样一条SQL,无论读取的表中数据是怎么样的,最后生成的执行计划都是一样的
在RBO中SQL写法的不同很有可能影响最终的执行计划,从而影响脚本性能
执行过程
Transformation遍历关系表达式,只要模式能够满足特定优化规则就进行转换,生成了一个逻辑执行计划,但这只是逻辑上可行
Build Physical Plan将逻辑执行计划
build成物理执行计划,即决定各个Operator的具体实现,如Join算子的具体实现选择
基于代价的优化器(Cost-Based Optimizer,CBO)
根据优化规则对关系表达式进行转换,一个关系表达式经过优化规则后会生成另外一个关系表达式,同时原有表达式也会保留,经过一系列转换后会生成多个执行计划,然后CBO会根据统计信息和代价模型(Cost Model)计算每个执行计划的Cost,从中挑选Cost最小的执行计划
CBO中有两个依赖:统计信息和代价模型,统计信息的准确与否、代价模型的合理与否都会影响CBO选择最优计划
执行过程
Exploration根据优化规则进行等价转换,生成等价关系表达式,此时原有关系表达式会被保留Build Physical Plan决定各个Operator的具体实现Find Best Plan根据统计信息计算各个执行计划的Cost,选择Cost最小的执行计划
CBO优于RBO的原因
RBO是一种只认规则,对数据不敏感的呆板的优化器,而在实际过程中,数据往往是有变化的,通过RBO生成的执行计划很有可能不是最优的
目前各大数据库和大数据计算引擎都倾向于使用CBO,例如Oracle、Hive、Spark、Flink等等