diff --git "a/content/zh/post/kangyang/openGauss\346\225\260\346\215\256\345\272\223\346\240\270\345\277\203\346\212\200\346\234\257--SQL\345\274\225\346\223\216\357\274\2102\357\274\211.md" "b/content/zh/post/kangyang/openGauss\346\225\260\346\215\256\345\272\223\346\240\270\345\277\203\346\212\200\346\234\257--SQL\345\274\225\346\223\216\357\274\2102\357\274\211.md" index 06c86a36a5e2e54451c46172850fe7ae5d550e02..d8075fc394b8052e0812b7ce495d6a2d3b8c5d6b 100644 --- "a/content/zh/post/kangyang/openGauss\346\225\260\346\215\256\345\272\223\346\240\270\345\277\203\346\212\200\346\234\257--SQL\345\274\225\346\223\216\357\274\2102\357\274\211.md" +++ "b/content/zh/post/kangyang/openGauss\346\225\260\346\215\256\345\272\223\346\240\270\345\277\203\346\212\200\346\234\257--SQL\345\274\225\346\223\216\357\274\2102\357\274\211.md" @@ -1,4 +1,4 @@ -+++ ++++ title = "openGauss数据库核心技术-SQL引擎(2)" date = "2020-07-27" tags = ["openGauss数据库核心技术"] @@ -17,13 +17,13 @@ times = "17:30" ## 前言 -​ 上期我们介绍了SQL解析的原理,并介绍了查询优化中查询读写的技术原理,本期将介绍路径搜索、代价计算的技术原理。 +​ 上期我们介绍了SQL解析的原理,并介绍了查询优化中查询重写的技术原理,本期将介绍路径搜索、代价计算的技术原理。 ## 路径搜索 -​ 优化器最核心的问题是针对某个SQL语句获得其最优解的问题,这个过程通常需要枚举SQL语句对应的解空间,也就是枚举不同的候选的执行路径,这些执行路径互相等价,但是执行效率不同,这对解空间中的这些执行路径计算它们的执行代价,最终可以获得一个最优的执行路径。依据候选执行路径的搜索方法的不同,将优化器的结构划分为如下几种模式: +​ 优化器最核心的问题是针对某个SQL语句获得其最优解,这个过程通常需要枚举SQL语句对应的解空间,也就是枚举不同的候选的执行路径,这些执行路径互相等价,但是执行效率不同,对解空间中的这些执行路径计算它们的执行代价,最终可以获得一个最优的执行路径。依据候选执行路径的搜索方法的不同,将优化器的结构划分为如下几种模式: -- 自底向上模式:如图1所示,自底向上的模式会对逻辑执行计划进行拆解,先建立对表的扫描算子,然后由扫描算子构成连接算子,最终堆成一个物理执行计划,在这个过程中,由于物理扫描算子和物理连接算子有多种可能,因此会生成多个物理执行路径,优化器会根据各个执行路径的估算代价选择出代价最低的执行计划,然后转交由执行器负责执行。 +- 自底向上模式:如图1所示,自底向上模式会对逻辑执行计划进行拆解,先建立对表的扫描算子,然后由扫描算子构成连接算子,最终堆成一个物理执行计划,在这个过程中,由于物理扫描算子和物理连接算子有多种可能,因此会生成多个物理执行路径,优化器会根据各个执行路径的估算代价选择出代价最低的执行计划,然后转交由执行器负责执行。 **图1 自底向上模式** @@ -35,7 +35,7 @@ times = "17:30" -- 自顶向下模式:该模式总体是运用面向对象思路,将优化器核心功能对象化,在词法分析、语法分析、语义分析后生成逻辑计划。基于此逻辑计划,应用对象化的优化规则,产生多个待选的逻辑计划,通过采用自顶向下的方法遍历逻辑计划,结合动态规划、代价估算和分支限界技术,获得最优的执行路径,如图2所示。 +- 自顶向下模式:如图2所示,该模式总体是运用面向对象思路,将优化器核心功能对象化,在词法分析、语法分析、语义分析后生成逻辑计划。基于此逻辑计划,应用对象化的优化规则,产生多个待选的逻辑计划,通过采用自顶向下的方法遍历逻辑计划,结合动态规划、代价估算和分支限界技术,获得最优的执行路径。 **图2 自顶向下模式** @@ -43,11 +43,11 @@ times = "17:30" ![](../figures/图2.png) -- 随机搜索模式:无论是自底向上模式还是自顶下模式,在参与连接的表的数量比较多的情况下,都会出现枚举时间过长的问题,一些优化器在表比较多的情况下通过一些随机枚举的方法对路径进行搜索,尝试在随机的解空间中获得次优的执行计划。 +- 随机搜索模式:无论是自底向上模式还是自顶向下模式,在参与连接的表的数量比较多的情况下,都会出现枚举时间过长的问题,一些优化器在表比较多的情况下通过一些随机枚举的方法对路径进行搜索,尝试在随机的解空间中获得次优的执行计划。 -​ 目前MySQL、PostgreSQL等数据库的优化器采用的是自底向上模式,SQL Server以及开源的Calcite、ORCA则采用了自顶向下的模式,其中Calcite以良好的扩展性被广泛应用到其他开源项目里包括Apache Storm、Apache Flink、Apache Kylin、Apache Drill、SQL- Gremlin等项目。openGauss采用的是自底向上模式和随机搜索模式相结合的方式。 +​ 目前MySQL、PostgreSQL等数据库的优化器采用的是自底向上模式,SQL Server以及开源的Calcite、ORCA则采用了自顶向下模式,其中Calcite以良好的扩展性被广泛应用到其他开源项目里包括Apache Storm、Apache Flink、Apache Kylin、Apache Drill、SQL- Gremlin等项目。openGauss采用的是自底向上模式和随机搜索模式相结合的方式。 -​ 无论是自顶向下的搜索模式还是自底向上的搜索模式,搜索的过程也都是一个从逻辑执行计划想物理执行计划转变的过程,例如针对每个表可以有不同的扫描算子,而逻辑连接算子也可以转换为多种不同的物理连接算子,下面介绍一下具体的物理算子。 +​ 无论是自顶向下的搜索模式还是自底向上的搜索模式,搜索的过程也都是一个从逻辑执行计划向物理执行计划转变的过程,例如针对每个表可以有不同的扫描算子,而逻辑连接算子也可以转换为多种不同的物理连接算子,下面介绍一下具体的物理算子。 ### 单表扫描路径搜索 @@ -58,14 +58,13 @@ openGauss采用的是自底向上的路径搜索方法,因此路径生成总 ​ 优化器首先根据表的数据量、过滤条件、可用的索引结合代价模型来估算各种不同扫描路径的代价。例如:给定表定义`CREATE TABLE t1(c1 int);`如果表中数据为1,2,3…100000000连续的整型值并且在c1列上有B+树索引,那么对于`SELECT * FROM t1 WHERE c1=1;`来说,只要读取1个索引页面和1个表页面就可以获取到数据。然而对于全表扫描,需要读取1亿条数据才能获取同样的结果。在这种情况下索引扫描的路径胜出。 -​ 索引扫描并不是在所有情况下都优于全表扫描,它们的优劣取决于过滤条件能够多滤掉多少数据,通常数据库管理系统会采用B+树来建立索引,如果在选择率比较高的情况下,B+树索引会带来大量的随机IO,这会降低索引扫描算子的访问效率。比如`SELECT * FROM t1 WHERE c1>0;`这条语句,索引扫描需要访问索引中的全部数据和表中的全部数据,并且带来巨量的随机IO,而全表扫描只需要顺序的访问表中的全部数据,因此在这种情况下,全表扫描的代价更低。 +​ 索引扫描并不是在所有情况下都优于全表扫描,它们的优劣取决于过滤条件能够多滤掉多少数据,通常数据库管理系统会采用B+树来建立索引,如果在选择率比较高的情况下,B+树索引会带来大量的随机I/O,这会降低索引扫描算子的访问效率。比如`SELECT * FROM t1 WHERE c1>0;`这条语句,索引扫描需要访问索引中的全部数据和表中的全部数据,并且带来巨量的随机I/O,而全表扫描只需要顺序的访问表中的全部数据,因此在这种情况下,全表扫描的代价更低。 ### 多表连接路径搜索 ​ 多表路径生成的难点主要在于如何枚举所有的表连接顺序(Join Reorder)和连接算法(Join Algorithm)。假设有两个表t1和t2做JOIN操作,根据关系代数中的交换律原则,可以枚举的连接顺序有t1 × t2和t2 × t1两种,JOIN的物理连接算子有Hash Join、Nested Loop Join、Merge Join三种类型。这样一来,可供选择的路径有6种之多。这个数量随着表的增多会呈指数级增长,因此高效的搜索算法显得至关重要。 -​ openGauss通常采用自底向上的路径搜索方法,首先生成了每个表的扫描路径,这些扫描路径在执行计划的最底层(第一层),在第二层开始考虑两表连接的最优路径,即枚举计算出每两表连接的可能性,在第三层考虑三表连接的最优路径,即枚举计算出三表连接的可能性,直到最顶层为止生成全局最优的执行计划。 -假设有4个表做JOIN操作,它们的的连接路径生成过程如下: +​ openGauss通常采用自底向上的路径搜索方法,首先生成了每个表的扫描路径,这些扫描路径在执行计划的最底层(第一层),在第二层开始考虑两表连接的最优路径,即枚举计算出每两表连接的可能性,在第三层考虑三表连接的最优路径,即枚举计算出三表连接的可能性,直到最顶层为止生成全局最优的执行计划。假设有4个表做JOIN操作,它们的连接路径生成过程如下: - 单表最优路径:依次生成{1},{2},{3},{4}单表的最优路径。 - 二表最优路径:依次生成{1 2},{1 3},{1 4},{2 3},{2 4},{3 4}的最优路径。 @@ -80,29 +79,8 @@ openGauss采用的是自底向上的路径搜索方法,因此路径生成总 - 在搜索的过程中基于代价估算对执行路径采用LowBound剪枝,放弃一些代价较高的执行路径。 - 保留具有特殊物理属性的执行路径,例如有些执行路径的结果具有有序性的特点,这些执行路径可能在后序的优化过程中避免再次排序。 -### 分布式路径搜索 -​ openGauss优化引擎可以生成高效的分布式路径。在分布式架构下,同一个表的数据会分布到不同的DN上,创建表的时候可以选择将数据在每个表上做Hash分布或者Random分布,为了正确执行两表连接操作,可能需要将两个表的数据做重新分布才能得到正确的连接结果,因此openGauss的分布式执行计划中增加了对数据进行重分布的两个算子: -- Redistribute:将一个表的数据按照执行的Hash值在所有的DN上做重分布。 -- Broadcast:通过广播的方式重新分布一个表的数据,保证广播之后每个DN上都有这个表的数据的一份副本。 - -分布式路径生成时,会考虑两表以及连接条件上的数据是否处于同一个数据节点,如果不是,那么会添加相应的数据分发算子。例如: - -``` -CREATE TABLE t1(c1 int, c2 int) DISTRIBUTE BY hash(c1); -CREATE TABLE t2(c1 int, c2 int) DISTRIBUTE BY hash(c2); -SELECT * FROM t1 JOIN t2 ON t1.c1=t2.c1; -``` - -其中表t1采用的是Hash分布方法,其分布键为c1列,表t2采用的也是Hash分布方法,其分布键为c2列,由于SELECT查询中选择条件是在t1.c1和t2.c2上做连接操作,这两个列的分布不同,因此做连接之前需要添加数据重分布来确保连接的数据在同一数据节点上。那么有如下几种可供选择的路径如图3所示。 - -**图3 分布式计划示例** - -![](../figures/图3.png) - - -根据分发算子所需要处理的数据量以及网络通信所带来的消耗,可以计算这些路径的代价,openGauss优化引擎会根据代价从中选出最优的路径。 ### 利用物理属性优化 @@ -196,24 +174,24 @@ P(A>5 and B<3) - 启动代价 从SQL语句开始执行,到此算子输出第一条元组为止,所需要的代价,称为启动代价。有的算子启动代价很小,比如基表上的扫描算子,一旦开始读取数据页,就可以输出元组,因此启动代价为0。有的算子的启动代价相对较大,比如排序算子,它需要把所有下层算子的输出全部读取到,并且把这些元组排序之后,才能输出第一条元组,因此它的启动代价比较大。 + - 执行代价 从输出第一条元组开始,至查询结束,所需要的代价,称为执行代价。这个代价中又可以包含CPU代价、IO代价和通信代价,执行代价的大小与算子需要处理的数据量有关,与每个算子完成的功能有关。处理的数据量越大、算子需要完成的任务越重,则执行代价越大。 + - 总代价 代价计算是一个自底向上的过程,首先计算扫描算子的代价,然后根据扫描算子的代价计算连接算子的代价以及Non-SPJ算子的代价。 -​ **图4 代价计算示例** - - - -​ ![](../figures/图4.png) - -​ 如图4所示,SQL查询中包含两张表,分别为t1、t2,它的某个候选计划的计算过程如下: + -1. 扫描t1的启动代价为0.00,总代价为13.13。总代价中既包括了扫描表页面的IO代价,也包括了对元组进行处理的CPU代价,同理可以获得对t2表扫描的代价。 -2. 由于连接条件(t1.c1 = t2.c2)中的列与两表的分布列不同,因此该计划对t2进行了广播(Broadcast),广播算子的总代价为15.18,此代价已经包括了顺序扫描t2的代价13.13。 -3. 使用Hash Join时,必须先为内表的数据建立Hash表,因此Hash Join具有启动代价,它的启动代价是13.29,Hash Join的总代价为28.64。 -4. 聚集算子的启动代价为28.69,总代价为28.79。 -5. 依次类推,此计划最终的启动代价为29.31,总代价为29.72。 +>注释 +> +>1. SPJ:关系代数中最基本的3个算子:选择(SELECTION)、投影(PROJECTION)、连接(JOIN): +> Selection 选择,例如select xxx from t where xx = 5里面的where过滤条件。 +> Projection 投影,select c from t里面的取c列是投影操作。 +> Join 连接,select xx from t1, t2 where t1.c = t2.c就是把t1 t2两个表做Join。 +>2. Non-SPJ: 除SPJ算子外的排序(Sort)、聚集(Aggregation)、集合(UNION/EXCEPT)操作等算子。 +> +> ## 小结