From a945dfe5733971d38a4bf8057055944f27165813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clawyer=E2=80=9Dlawyerphx=E2=80=9C?= Date: Thu, 21 May 2020 11:53:21 +0800 Subject: [PATCH 01/12] add documents about column-pruning.md --- column-pruning.md | 109 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/column-pruning.md b/column-pruning.md index 61250864ad89..4537ab90db52 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -3,4 +3,111 @@ title: 列裁剪 category: performance --- -# 列裁剪 \ No newline at end of file +# 列裁剪 + +列裁剪的基本思想在于:对于算子中实际用不上的列,优化器在优化的过程中没有必要保留它们。 对这些列的删除会减少 I/O 资源占用,并为后续的优化带来便利。下面给出一个列重复的例子: + +假设表 t 里面有 a b c d 四列,执行如下语句: + +```sql +select a from t where b > 5 +``` + +在该查询的过程中,t 表实际上只有 a, b 两列会被用到,而 c, d 的数据则显得多余。对应到该语句的查询计划,Selection 算子会用到 b 列,下面接着的 DataSource 算子会用到 a, b 两列,而剩下 c, d 两列则都可以裁剪掉,DataSource 算子在读数据时不需要将它们读进来。 + +出于上述考量,TiDB 会在逻辑优化阶段进行自上而下的扫描,裁剪不需要的列,减少资源浪费。该扫描过程称作 “列裁剪”,对应逻辑优化规则中的 `columnPruner`。 + +## 算法实现 + +对于逻辑计划中的一个算子而言,它所需要的列分为两种:上层算子计算时所需要的列,本层算子计算时所需要的列。从这个原则出发,优化器可以自顶向下地计算每个算子所需要的列,在遍历的过程中,维护逻辑计划树中的祖先节点给当前算子带来的“列需求”。 + +相关代码实现于 `planner/core/` 目录下的 `plan.go` 与 `rule_column_pruning.go` 中,实现接口如下。 + +```goland +PruneColumns([]*expression.Column) error +``` + +函数输入即为施加给当前算子的“列需求”集合。 在普通优化器中,列裁剪会发生在逻辑优化的开始和结尾,[相关代码](https://github.com/pingcap/tidb/blob/902231076d56fee9074e4c7bcd03a0d0f0d88524/planner/core/optimizer.go#L61)。 在 cascades 优化器中,列裁剪会发生在 Preprocessing 阶段,[相关代码](https://github.com/pingcap/tidb/blob/ded862fbebc555de98e230ef57310f9162725a9e/planner/cascades/optimize.go#L118)。 + + +## 各算子的列裁剪实现: + +本章节将对于目前实现的所有列裁剪方式进行详细介绍。默认情况下,优化器会在当前算子的列裁剪完成之后将列需求集合传递下去。 + +#### baseLogicalPlan +对于默认算子,优化器不进行列裁剪。 + +#### LogicalProjection + +如果投影的结果中含有上层算子所不需要的列,那么这些列就应该删除掉。基于这个原则,优化器会去除当前算子中不在 列需求集合 中的列。(特别的,对于被赋值或sleep的表达式所涵盖的列,优化器不予删除) + +同时投影的列需求和上层算子无关,优化器采用当前投影算子所需要的列作为传递下去的列需求集合。 + + +#### LogicalSelection +select 算子的条件语句会引入一些新的列,这些列会被优化器加入下传的列需求集合中。 + +#### LogicalAggregation + +aggregation 算子的列裁剪相对较为复杂,大致可以分为以下四个阶段: +首先,优化器对于当前算子中的聚合函数进行扫描,如果一个聚合函数的结果没有被上层算子使用,那么就删除这个聚合函数。 + +接着,优化器遍历所有的列,将各聚合函数所涉及到的列加入下传的列需求集合。 + +此时,如果所有的聚合函数都在第一阶段被删除掉,优化器会加入一个类型为 FirstRowColumn 的默认聚合函数,来保证逻辑计划的正确性。 + +最后,优化器遍历当前算子的 group by 条件,对 group by 条件进行裁剪,去除常量和 Null,并将必要列加入下传的列需求集合中。 + +#### LogicalSort, LogicalTopN, +对于排序和 topN 算子,和 aggregation 算子裁剪的第四阶段类似,优化器会对于当前算子的 by 条件进行裁剪,去除常量和 Null,并将必要列加入下传的列需求集合中。 + +#### LogicalUnionAll +对于 UnionAll 算子,其裁剪发生在子算子的裁剪之后,分为两种情况: + +如果当前 UnionAll 算子的列不为上层算子所用,优化器不对当前算子进行裁剪,并以当前 UnionAll 算子作为基准,以其本身包含的列作为下传的列需求集合传到下层算子。 + +如果当前 UnionAll 算子的列为上层算子所用,优化器则直接将列需求集合传递下去,并在最后调整 UnionAll 算子的列和子算子保持一致。 + +#### LogicalUnionScan +对于 UnionScan 算子,优化器直接在列需求集合中加入当前算子的 handle columns,不进行列裁剪。 + +#### DataSource + +在优化器中,经过上层算子的层层裁剪,列需求最终被传到 Datasource 算子上,并由其做最后的裁剪工作。该算子的裁剪不需要计算下传的列需求,只需对当前算子进行裁剪即可,该裁剪工作大致分为三步: + +首先,优化器会删除既没有被上层算子需要,又没有被当前算子的条件语句所需要的列。 + +此时,如果所有列都被删除了,则加入 handle columns 来补足。 + +最后,优化器扫描 handle columns ,对于没有对应 index 的 handle column 进行删除。 + +#### LogicalTableDual +类似 Datasource 算子,TableDual 算子也只需要对当前算子进行裁剪。不同在于第一步:优化器在裁剪时会删去当前算子中没有被上层算子用过的列。 + +#### LogicalJoin +相对其他算子,Join 算子的列裁剪比较特殊,因为 Join 算子的列是由 Join 的两个对象决定的。同时,由于其独特的性质, Join 算子本身的列裁剪不会对下层算子的列需求产生影响。因此,在优化器中,对于 Join 算子本身列的裁剪发生在下层算子的裁剪之后。具体步骤分为三步: + +第一步:优化器从 Join 的条件语句中提取出左右算子的列需求集合,并用其对于左右算子分别进行列裁剪。 + +第二步:优化器将左右算子裁剪后得到的列进行合并。 + +第三步:优化器对于合并得到的列进行裁剪。 + +#### LogicalApply +LogicalApply 算子的裁剪类似于 LogicalJoin 算子的裁剪,也是先进行左右两列的裁剪。不同在于第一个阶段的执行:由于 LogicalApply 存在执行先后顺序 以及 存在两个算子间列需求的传递,优化器会先执行右侧算子的列裁剪,并将右侧列裁剪得到的结果放入左侧算子的列需求中。 + +#### LogicalLock + +对于不是 Select Lock For Update 的 LogicalLock 算子,优化器直接按照 baseLogicalPlan 的处理方式进行处理。而对于符合条件的算子,分为两种情况进行处理: + +如果当前算子对应 partitioned table,出于计算 lock key 的考量,优化器会保留当前算子的所有列,直接将这些列作为下传的列需求。 + +如果当前算子不对应 partitioned table,直接将 table Id 作为 handle column 加入当前的列需求集合,作为下传的列需求集合。 + +#### LogicalWindow + +对于 Window 算子,其本身的信息的裁剪取决于下层阶段的裁剪,所以优化器会先进行其子算子的裁剪。具体流程分为两步: + +首先,优化器会先利用 Window 算子所需要的列对下传来的列需求集合进行调整,只保留共同需要的列作为下传的列需求集合。 + +接下里,在下层算子的列裁剪完成后,优化器会反过来调整 Window 算子的表信息,让其和下层保持一致。 \ No newline at end of file From e0ff8ded3e35575093787f091d75c14315be6594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clawyer=E2=80=9Dlawyerphx=E2=80=9C?= Date: Thu, 21 May 2020 12:05:56 +0800 Subject: [PATCH 02/12] refine format --- column-pruning.md | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/column-pruning.md b/column-pruning.md index 4537ab90db52..34f27035705c 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -29,25 +29,25 @@ PruneColumns([]*expression.Column) error 函数输入即为施加给当前算子的“列需求”集合。 在普通优化器中,列裁剪会发生在逻辑优化的开始和结尾,[相关代码](https://github.com/pingcap/tidb/blob/902231076d56fee9074e4c7bcd03a0d0f0d88524/planner/core/optimizer.go#L61)。 在 cascades 优化器中,列裁剪会发生在 Preprocessing 阶段,[相关代码](https://github.com/pingcap/tidb/blob/ded862fbebc555de98e230ef57310f9162725a9e/planner/cascades/optimize.go#L118)。 - -## 各算子的列裁剪实现: +## 各算子的列裁剪实现 本章节将对于目前实现的所有列裁剪方式进行详细介绍。默认情况下,优化器会在当前算子的列裁剪完成之后将列需求集合传递下去。 -#### baseLogicalPlan +### baseLogicalPlan + 对于默认算子,优化器不进行列裁剪。 -#### LogicalProjection +### LogicalProjection 如果投影的结果中含有上层算子所不需要的列,那么这些列就应该删除掉。基于这个原则,优化器会去除当前算子中不在 列需求集合 中的列。(特别的,对于被赋值或sleep的表达式所涵盖的列,优化器不予删除) 同时投影的列需求和上层算子无关,优化器采用当前投影算子所需要的列作为传递下去的列需求集合。 +### LogicalSelection -#### LogicalSelection select 算子的条件语句会引入一些新的列,这些列会被优化器加入下传的列需求集合中。 -#### LogicalAggregation +### LogicalAggregation aggregation 算子的列裁剪相对较为复杂,大致可以分为以下四个阶段: 首先,优化器对于当前算子中的聚合函数进行扫描,如果一个聚合函数的结果没有被上层算子使用,那么就删除这个聚合函数。 @@ -58,20 +58,23 @@ aggregation 算子的列裁剪相对较为复杂,大致可以分为以下四 最后,优化器遍历当前算子的 group by 条件,对 group by 条件进行裁剪,去除常量和 Null,并将必要列加入下传的列需求集合中。 -#### LogicalSort, LogicalTopN, +### LogicalSort 和 LogicalTopN + 对于排序和 topN 算子,和 aggregation 算子裁剪的第四阶段类似,优化器会对于当前算子的 by 条件进行裁剪,去除常量和 Null,并将必要列加入下传的列需求集合中。 -#### LogicalUnionAll +### LogicalUnionAll + 对于 UnionAll 算子,其裁剪发生在子算子的裁剪之后,分为两种情况: 如果当前 UnionAll 算子的列不为上层算子所用,优化器不对当前算子进行裁剪,并以当前 UnionAll 算子作为基准,以其本身包含的列作为下传的列需求集合传到下层算子。 如果当前 UnionAll 算子的列为上层算子所用,优化器则直接将列需求集合传递下去,并在最后调整 UnionAll 算子的列和子算子保持一致。 -#### LogicalUnionScan +### LogicalUnionScan + 对于 UnionScan 算子,优化器直接在列需求集合中加入当前算子的 handle columns,不进行列裁剪。 -#### DataSource +### DataSource 在优化器中,经过上层算子的层层裁剪,列需求最终被传到 Datasource 算子上,并由其做最后的裁剪工作。该算子的裁剪不需要计算下传的列需求,只需对当前算子进行裁剪即可,该裁剪工作大致分为三步: @@ -81,10 +84,12 @@ aggregation 算子的列裁剪相对较为复杂,大致可以分为以下四 最后,优化器扫描 handle columns ,对于没有对应 index 的 handle column 进行删除。 -#### LogicalTableDual +### LogicalTableDual + 类似 Datasource 算子,TableDual 算子也只需要对当前算子进行裁剪。不同在于第一步:优化器在裁剪时会删去当前算子中没有被上层算子用过的列。 -#### LogicalJoin +### LogicalJoin + 相对其他算子,Join 算子的列裁剪比较特殊,因为 Join 算子的列是由 Join 的两个对象决定的。同时,由于其独特的性质, Join 算子本身的列裁剪不会对下层算子的列需求产生影响。因此,在优化器中,对于 Join 算子本身列的裁剪发生在下层算子的裁剪之后。具体步骤分为三步: 第一步:优化器从 Join 的条件语句中提取出左右算子的列需求集合,并用其对于左右算子分别进行列裁剪。 @@ -93,10 +98,11 @@ aggregation 算子的列裁剪相对较为复杂,大致可以分为以下四 第三步:优化器对于合并得到的列进行裁剪。 -#### LogicalApply +### LogicalApply + LogicalApply 算子的裁剪类似于 LogicalJoin 算子的裁剪,也是先进行左右两列的裁剪。不同在于第一个阶段的执行:由于 LogicalApply 存在执行先后顺序 以及 存在两个算子间列需求的传递,优化器会先执行右侧算子的列裁剪,并将右侧列裁剪得到的结果放入左侧算子的列需求中。 -#### LogicalLock +### LogicalLock 对于不是 Select Lock For Update 的 LogicalLock 算子,优化器直接按照 baseLogicalPlan 的处理方式进行处理。而对于符合条件的算子,分为两种情况进行处理: @@ -104,7 +110,7 @@ LogicalApply 算子的裁剪类似于 LogicalJoin 算子的裁剪,也是先进 如果当前算子不对应 partitioned table,直接将 table Id 作为 handle column 加入当前的列需求集合,作为下传的列需求集合。 -#### LogicalWindow +### LogicalWindow 对于 Window 算子,其本身的信息的裁剪取决于下层阶段的裁剪,所以优化器会先进行其子算子的裁剪。具体流程分为两步: From dfc4aff7cfed2bbb20a7bc01f385e15dffc14e98 Mon Sep 17 00:00:00 2001 From: lawyerphx Date: Thu, 21 May 2020 14:28:33 +0800 Subject: [PATCH 03/12] remove detailed intro --- column-pruning.md | 89 ----------------------------------------------- 1 file changed, 89 deletions(-) diff --git a/column-pruning.md b/column-pruning.md index 34f27035705c..f2e6206c1713 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -28,92 +28,3 @@ PruneColumns([]*expression.Column) error ``` 函数输入即为施加给当前算子的“列需求”集合。 在普通优化器中,列裁剪会发生在逻辑优化的开始和结尾,[相关代码](https://github.com/pingcap/tidb/blob/902231076d56fee9074e4c7bcd03a0d0f0d88524/planner/core/optimizer.go#L61)。 在 cascades 优化器中,列裁剪会发生在 Preprocessing 阶段,[相关代码](https://github.com/pingcap/tidb/blob/ded862fbebc555de98e230ef57310f9162725a9e/planner/cascades/optimize.go#L118)。 - -## 各算子的列裁剪实现 - -本章节将对于目前实现的所有列裁剪方式进行详细介绍。默认情况下,优化器会在当前算子的列裁剪完成之后将列需求集合传递下去。 - -### baseLogicalPlan - -对于默认算子,优化器不进行列裁剪。 - -### LogicalProjection - -如果投影的结果中含有上层算子所不需要的列,那么这些列就应该删除掉。基于这个原则,优化器会去除当前算子中不在 列需求集合 中的列。(特别的,对于被赋值或sleep的表达式所涵盖的列,优化器不予删除) - -同时投影的列需求和上层算子无关,优化器采用当前投影算子所需要的列作为传递下去的列需求集合。 - -### LogicalSelection - -select 算子的条件语句会引入一些新的列,这些列会被优化器加入下传的列需求集合中。 - -### LogicalAggregation - -aggregation 算子的列裁剪相对较为复杂,大致可以分为以下四个阶段: -首先,优化器对于当前算子中的聚合函数进行扫描,如果一个聚合函数的结果没有被上层算子使用,那么就删除这个聚合函数。 - -接着,优化器遍历所有的列,将各聚合函数所涉及到的列加入下传的列需求集合。 - -此时,如果所有的聚合函数都在第一阶段被删除掉,优化器会加入一个类型为 FirstRowColumn 的默认聚合函数,来保证逻辑计划的正确性。 - -最后,优化器遍历当前算子的 group by 条件,对 group by 条件进行裁剪,去除常量和 Null,并将必要列加入下传的列需求集合中。 - -### LogicalSort 和 LogicalTopN - -对于排序和 topN 算子,和 aggregation 算子裁剪的第四阶段类似,优化器会对于当前算子的 by 条件进行裁剪,去除常量和 Null,并将必要列加入下传的列需求集合中。 - -### LogicalUnionAll - -对于 UnionAll 算子,其裁剪发生在子算子的裁剪之后,分为两种情况: - -如果当前 UnionAll 算子的列不为上层算子所用,优化器不对当前算子进行裁剪,并以当前 UnionAll 算子作为基准,以其本身包含的列作为下传的列需求集合传到下层算子。 - -如果当前 UnionAll 算子的列为上层算子所用,优化器则直接将列需求集合传递下去,并在最后调整 UnionAll 算子的列和子算子保持一致。 - -### LogicalUnionScan - -对于 UnionScan 算子,优化器直接在列需求集合中加入当前算子的 handle columns,不进行列裁剪。 - -### DataSource - -在优化器中,经过上层算子的层层裁剪,列需求最终被传到 Datasource 算子上,并由其做最后的裁剪工作。该算子的裁剪不需要计算下传的列需求,只需对当前算子进行裁剪即可,该裁剪工作大致分为三步: - -首先,优化器会删除既没有被上层算子需要,又没有被当前算子的条件语句所需要的列。 - -此时,如果所有列都被删除了,则加入 handle columns 来补足。 - -最后,优化器扫描 handle columns ,对于没有对应 index 的 handle column 进行删除。 - -### LogicalTableDual - -类似 Datasource 算子,TableDual 算子也只需要对当前算子进行裁剪。不同在于第一步:优化器在裁剪时会删去当前算子中没有被上层算子用过的列。 - -### LogicalJoin - -相对其他算子,Join 算子的列裁剪比较特殊,因为 Join 算子的列是由 Join 的两个对象决定的。同时,由于其独特的性质, Join 算子本身的列裁剪不会对下层算子的列需求产生影响。因此,在优化器中,对于 Join 算子本身列的裁剪发生在下层算子的裁剪之后。具体步骤分为三步: - -第一步:优化器从 Join 的条件语句中提取出左右算子的列需求集合,并用其对于左右算子分别进行列裁剪。 - -第二步:优化器将左右算子裁剪后得到的列进行合并。 - -第三步:优化器对于合并得到的列进行裁剪。 - -### LogicalApply - -LogicalApply 算子的裁剪类似于 LogicalJoin 算子的裁剪,也是先进行左右两列的裁剪。不同在于第一个阶段的执行:由于 LogicalApply 存在执行先后顺序 以及 存在两个算子间列需求的传递,优化器会先执行右侧算子的列裁剪,并将右侧列裁剪得到的结果放入左侧算子的列需求中。 - -### LogicalLock - -对于不是 Select Lock For Update 的 LogicalLock 算子,优化器直接按照 baseLogicalPlan 的处理方式进行处理。而对于符合条件的算子,分为两种情况进行处理: - -如果当前算子对应 partitioned table,出于计算 lock key 的考量,优化器会保留当前算子的所有列,直接将这些列作为下传的列需求。 - -如果当前算子不对应 partitioned table,直接将 table Id 作为 handle column 加入当前的列需求集合,作为下传的列需求集合。 - -### LogicalWindow - -对于 Window 算子,其本身的信息的裁剪取决于下层阶段的裁剪,所以优化器会先进行其子算子的裁剪。具体流程分为两步: - -首先,优化器会先利用 Window 算子所需要的列对下传来的列需求集合进行调整,只保留共同需要的列作为下传的列需求集合。 - -接下里,在下层算子的列裁剪完成后,优化器会反过来调整 Window 算子的表信息,让其和下层保持一致。 \ No newline at end of file From 175fc13c748845114e0c481cb6f8d7e43fc7b2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clawyer=E2=80=9Dlawyerphx=E2=80=9C?= Date: Thu, 21 May 2020 14:37:31 +0800 Subject: [PATCH 04/12] add some details. --- column-pruning.md | 64 ++++++----------------------------------------- 1 file changed, 7 insertions(+), 57 deletions(-) diff --git a/column-pruning.md b/column-pruning.md index 34f27035705c..58367f741a3f 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -29,25 +29,21 @@ PruneColumns([]*expression.Column) error 函数输入即为施加给当前算子的“列需求”集合。 在普通优化器中,列裁剪会发生在逻辑优化的开始和结尾,[相关代码](https://github.com/pingcap/tidb/blob/902231076d56fee9074e4c7bcd03a0d0f0d88524/planner/core/optimizer.go#L61)。 在 cascades 优化器中,列裁剪会发生在 Preprocessing 阶段,[相关代码](https://github.com/pingcap/tidb/blob/ded862fbebc555de98e230ef57310f9162725a9e/planner/cascades/optimize.go#L118)。 -## 各算子的列裁剪实现 +## 常见算子的裁剪方法 -本章节将对于目前实现的所有列裁剪方式进行详细介绍。默认情况下,优化器会在当前算子的列裁剪完成之后将列需求集合传递下去。 +本章节将对于用户常见的几个算子进行列裁剪方法简介。默认情况下,优化器不对于当前算子进行列裁剪。 -### baseLogicalPlan - -对于默认算子,优化器不进行列裁剪。 - -### LogicalProjection +### Projection 如果投影的结果中含有上层算子所不需要的列,那么这些列就应该删除掉。基于这个原则,优化器会去除当前算子中不在 列需求集合 中的列。(特别的,对于被赋值或sleep的表达式所涵盖的列,优化器不予删除) 同时投影的列需求和上层算子无关,优化器采用当前投影算子所需要的列作为传递下去的列需求集合。 -### LogicalSelection +### Selection select 算子的条件语句会引入一些新的列,这些列会被优化器加入下传的列需求集合中。 -### LogicalAggregation +### Aggregation aggregation 算子的列裁剪相对较为复杂,大致可以分为以下四个阶段: 首先,优化器对于当前算子中的聚合函数进行扫描,如果一个聚合函数的结果没有被上层算子使用,那么就删除这个聚合函数。 @@ -58,37 +54,11 @@ aggregation 算子的列裁剪相对较为复杂,大致可以分为以下四 最后,优化器遍历当前算子的 group by 条件,对 group by 条件进行裁剪,去除常量和 Null,并将必要列加入下传的列需求集合中。 -### LogicalSort 和 LogicalTopN +### Sort 和 TopN 对于排序和 topN 算子,和 aggregation 算子裁剪的第四阶段类似,优化器会对于当前算子的 by 条件进行裁剪,去除常量和 Null,并将必要列加入下传的列需求集合中。 -### LogicalUnionAll - -对于 UnionAll 算子,其裁剪发生在子算子的裁剪之后,分为两种情况: - -如果当前 UnionAll 算子的列不为上层算子所用,优化器不对当前算子进行裁剪,并以当前 UnionAll 算子作为基准,以其本身包含的列作为下传的列需求集合传到下层算子。 - -如果当前 UnionAll 算子的列为上层算子所用,优化器则直接将列需求集合传递下去,并在最后调整 UnionAll 算子的列和子算子保持一致。 - -### LogicalUnionScan - -对于 UnionScan 算子,优化器直接在列需求集合中加入当前算子的 handle columns,不进行列裁剪。 - -### DataSource - -在优化器中,经过上层算子的层层裁剪,列需求最终被传到 Datasource 算子上,并由其做最后的裁剪工作。该算子的裁剪不需要计算下传的列需求,只需对当前算子进行裁剪即可,该裁剪工作大致分为三步: - -首先,优化器会删除既没有被上层算子需要,又没有被当前算子的条件语句所需要的列。 - -此时,如果所有列都被删除了,则加入 handle columns 来补足。 - -最后,优化器扫描 handle columns ,对于没有对应 index 的 handle column 进行删除。 - -### LogicalTableDual - -类似 Datasource 算子,TableDual 算子也只需要对当前算子进行裁剪。不同在于第一步:优化器在裁剪时会删去当前算子中没有被上层算子用过的列。 - -### LogicalJoin +### Join 相对其他算子,Join 算子的列裁剪比较特殊,因为 Join 算子的列是由 Join 的两个对象决定的。同时,由于其独特的性质, Join 算子本身的列裁剪不会对下层算子的列需求产生影响。因此,在优化器中,对于 Join 算子本身列的裁剪发生在下层算子的裁剪之后。具体步骤分为三步: @@ -97,23 +67,3 @@ aggregation 算子的列裁剪相对较为复杂,大致可以分为以下四 第二步:优化器将左右算子裁剪后得到的列进行合并。 第三步:优化器对于合并得到的列进行裁剪。 - -### LogicalApply - -LogicalApply 算子的裁剪类似于 LogicalJoin 算子的裁剪,也是先进行左右两列的裁剪。不同在于第一个阶段的执行:由于 LogicalApply 存在执行先后顺序 以及 存在两个算子间列需求的传递,优化器会先执行右侧算子的列裁剪,并将右侧列裁剪得到的结果放入左侧算子的列需求中。 - -### LogicalLock - -对于不是 Select Lock For Update 的 LogicalLock 算子,优化器直接按照 baseLogicalPlan 的处理方式进行处理。而对于符合条件的算子,分为两种情况进行处理: - -如果当前算子对应 partitioned table,出于计算 lock key 的考量,优化器会保留当前算子的所有列,直接将这些列作为下传的列需求。 - -如果当前算子不对应 partitioned table,直接将 table Id 作为 handle column 加入当前的列需求集合,作为下传的列需求集合。 - -### LogicalWindow - -对于 Window 算子,其本身的信息的裁剪取决于下层阶段的裁剪,所以优化器会先进行其子算子的裁剪。具体流程分为两步: - -首先,优化器会先利用 Window 算子所需要的列对下传来的列需求集合进行调整,只保留共同需要的列作为下传的列需求集合。 - -接下里,在下层算子的列裁剪完成后,优化器会反过来调整 Window 算子的表信息,让其和下层保持一致。 \ No newline at end of file From 39d5c039422c560ce73c6eb82dcb1d2a2ba0e0df Mon Sep 17 00:00:00 2001 From: lawyerphx Date: Thu, 21 May 2020 17:17:11 +0800 Subject: [PATCH 05/12] Update column-pruning.md Co-authored-by: toutdesuite --- column-pruning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/column-pruning.md b/column-pruning.md index 58367f741a3f..87dcdd6c0af4 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -5,7 +5,7 @@ category: performance # 列裁剪 -列裁剪的基本思想在于:对于算子中实际用不上的列,优化器在优化的过程中没有必要保留它们。 对这些列的删除会减少 I/O 资源占用,并为后续的优化带来便利。下面给出一个列重复的例子: +本文将介绍 SQL 逻辑优化中的列裁剪。列裁剪的基本思想是:对于算子中实际不会用到的列,优化器在优化的过程中没有必要保留这些列。删除这些列可以减少 I/O 资源占用,并为后续的优化带来便利。下面是一个列重复的例子: 假设表 t 里面有 a b c d 四列,执行如下语句: From 74c134e93eabf2f303aa3bc489760e118594afc1 Mon Sep 17 00:00:00 2001 From: lawyerphx Date: Thu, 21 May 2020 17:17:19 +0800 Subject: [PATCH 06/12] Update column-pruning.md Co-authored-by: toutdesuite --- column-pruning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/column-pruning.md b/column-pruning.md index 87dcdd6c0af4..88e70cc404cf 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -7,7 +7,7 @@ category: performance 本文将介绍 SQL 逻辑优化中的列裁剪。列裁剪的基本思想是:对于算子中实际不会用到的列,优化器在优化的过程中没有必要保留这些列。删除这些列可以减少 I/O 资源占用,并为后续的优化带来便利。下面是一个列重复的例子: -假设表 t 里面有 a b c d 四列,执行如下语句: +假设表 t 含有 a、b、c、d 四列,执行如下语句: ```sql select a from t where b > 5 From af85691eb244b24e4b84c14089b98ab1ffcb9888 Mon Sep 17 00:00:00 2001 From: lawyerphx Date: Thu, 21 May 2020 17:18:05 +0800 Subject: [PATCH 07/12] Update column-pruning.md Co-authored-by: toutdesuite --- column-pruning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/column-pruning.md b/column-pruning.md index 88e70cc404cf..e2c1b4791e3c 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -13,7 +13,7 @@ category: performance select a from t where b > 5 ``` -在该查询的过程中,t 表实际上只有 a, b 两列会被用到,而 c, d 的数据则显得多余。对应到该语句的查询计划,Selection 算子会用到 b 列,下面接着的 DataSource 算子会用到 a, b 两列,而剩下 c, d 两列则都可以裁剪掉,DataSource 算子在读数据时不需要将它们读进来。 +在该查询的过程中,表 t 实际上只有 a,、b 两列会被用到,不会用到 c、d 列的数据。对应到该语句的查询计划,Selection 算子会用到 b 列,之后 DataSource 算子会用到 a、b 两列。而 c、d 两列则都可以裁剪掉,DataSource 算子在读数据时不需要将它们读进来。 出于上述考量,TiDB 会在逻辑优化阶段进行自上而下的扫描,裁剪不需要的列,减少资源浪费。该扫描过程称作 “列裁剪”,对应逻辑优化规则中的 `columnPruner`。 From c71c50e88902f667fcd2f1fc0708ea3bd1ab5c8a Mon Sep 17 00:00:00 2001 From: lawyerphx Date: Thu, 21 May 2020 17:18:20 +0800 Subject: [PATCH 08/12] Update column-pruning.md Co-authored-by: toutdesuite --- column-pruning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/column-pruning.md b/column-pruning.md index e2c1b4791e3c..92013427a5a3 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -31,7 +31,7 @@ PruneColumns([]*expression.Column) error ## 常见算子的裁剪方法 -本章节将对于用户常见的几个算子进行列裁剪方法简介。默认情况下,优化器不对于当前算子进行列裁剪。 +本章节将介绍几种常见的算子的裁剪方法。默认情况下,优化器不对当前算子进行列裁剪。 ### Projection From ec699f01c66ba0017e0880a772d874805aba2035 Mon Sep 17 00:00:00 2001 From: lawyerphx Date: Thu, 21 May 2020 17:18:35 +0800 Subject: [PATCH 09/12] Update column-pruning.md Co-authored-by: toutdesuite --- column-pruning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/column-pruning.md b/column-pruning.md index 92013427a5a3..13d7c614e9b4 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -19,7 +19,7 @@ select a from t where b > 5 ## 算法实现 -对于逻辑计划中的一个算子而言,它所需要的列分为两种:上层算子计算时所需要的列,本层算子计算时所需要的列。从这个原则出发,优化器可以自顶向下地计算每个算子所需要的列,在遍历的过程中,维护逻辑计划树中的祖先节点给当前算子带来的“列需求”。 +对于逻辑计划中的一个算子,其所需要的列分为两种:上层算子计算时所需要的列,本层算子计算时所需要的列。从这个原则出发,优化器可以自上向下地计算每个算子所需要的列,在遍历的过程中,维护逻辑计划树中的祖先节点给当前算子带来的“列需求”。 相关代码实现于 `planner/core/` 目录下的 `plan.go` 与 `rule_column_pruning.go` 中,实现接口如下。 From d9aae46e2c658115e1d6b4b7ef3f04e2f51b3c8d Mon Sep 17 00:00:00 2001 From: lawyerphx Date: Thu, 21 May 2020 17:18:43 +0800 Subject: [PATCH 10/12] Update column-pruning.md Co-authored-by: toutdesuite --- column-pruning.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/column-pruning.md b/column-pruning.md index 13d7c614e9b4..b1ca8356ed16 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -15,7 +15,7 @@ select a from t where b > 5 在该查询的过程中,表 t 实际上只有 a,、b 两列会被用到,不会用到 c、d 列的数据。对应到该语句的查询计划,Selection 算子会用到 b 列,之后 DataSource 算子会用到 a、b 两列。而 c、d 两列则都可以裁剪掉,DataSource 算子在读数据时不需要将它们读进来。 -出于上述考量,TiDB 会在逻辑优化阶段进行自上而下的扫描,裁剪不需要的列,减少资源浪费。该扫描过程称作 “列裁剪”,对应逻辑优化规则中的 `columnPruner`。 +出于上述考量,TiDB 会在逻辑优化阶段进行自上而下的扫描,裁剪不需要的列,减少资源浪费。该扫描过程称作“列裁剪”,对应逻辑优化规则中的 `columnPruner`。 ## 算法实现 From 569136da7506ce59a5f44de86a2bbb3f91b4ad71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clawyer=E2=80=9Dlawyerphx=E2=80=9C?= Date: Thu, 21 May 2020 17:23:40 +0800 Subject: [PATCH 11/12] handle comments --- column-pruning.md | 54 +++-------------------------------------------- 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/column-pruning.md b/column-pruning.md index 58367f741a3f..a7a5626d79d1 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -9,61 +9,13 @@ category: performance 假设表 t 里面有 a b c d 四列,执行如下语句: +{{< copyable "sql" >}} + ```sql select a from t where b > 5 ``` 在该查询的过程中,t 表实际上只有 a, b 两列会被用到,而 c, d 的数据则显得多余。对应到该语句的查询计划,Selection 算子会用到 b 列,下面接着的 DataSource 算子会用到 a, b 两列,而剩下 c, d 两列则都可以裁剪掉,DataSource 算子在读数据时不需要将它们读进来。 -出于上述考量,TiDB 会在逻辑优化阶段进行自上而下的扫描,裁剪不需要的列,减少资源浪费。该扫描过程称作 “列裁剪”,对应逻辑优化规则中的 `columnPruner`。 - -## 算法实现 - -对于逻辑计划中的一个算子而言,它所需要的列分为两种:上层算子计算时所需要的列,本层算子计算时所需要的列。从这个原则出发,优化器可以自顶向下地计算每个算子所需要的列,在遍历的过程中,维护逻辑计划树中的祖先节点给当前算子带来的“列需求”。 - -相关代码实现于 `planner/core/` 目录下的 `plan.go` 与 `rule_column_pruning.go` 中,实现接口如下。 - -```goland -PruneColumns([]*expression.Column) error -``` - -函数输入即为施加给当前算子的“列需求”集合。 在普通优化器中,列裁剪会发生在逻辑优化的开始和结尾,[相关代码](https://github.com/pingcap/tidb/blob/902231076d56fee9074e4c7bcd03a0d0f0d88524/planner/core/optimizer.go#L61)。 在 cascades 优化器中,列裁剪会发生在 Preprocessing 阶段,[相关代码](https://github.com/pingcap/tidb/blob/ded862fbebc555de98e230ef57310f9162725a9e/planner/cascades/optimize.go#L118)。 - -## 常见算子的裁剪方法 - -本章节将对于用户常见的几个算子进行列裁剪方法简介。默认情况下,优化器不对于当前算子进行列裁剪。 - -### Projection - -如果投影的结果中含有上层算子所不需要的列,那么这些列就应该删除掉。基于这个原则,优化器会去除当前算子中不在 列需求集合 中的列。(特别的,对于被赋值或sleep的表达式所涵盖的列,优化器不予删除) - -同时投影的列需求和上层算子无关,优化器采用当前投影算子所需要的列作为传递下去的列需求集合。 - -### Selection - -select 算子的条件语句会引入一些新的列,这些列会被优化器加入下传的列需求集合中。 - -### Aggregation - -aggregation 算子的列裁剪相对较为复杂,大致可以分为以下四个阶段: -首先,优化器对于当前算子中的聚合函数进行扫描,如果一个聚合函数的结果没有被上层算子使用,那么就删除这个聚合函数。 - -接着,优化器遍历所有的列,将各聚合函数所涉及到的列加入下传的列需求集合。 - -此时,如果所有的聚合函数都在第一阶段被删除掉,优化器会加入一个类型为 FirstRowColumn 的默认聚合函数,来保证逻辑计划的正确性。 - -最后,优化器遍历当前算子的 group by 条件,对 group by 条件进行裁剪,去除常量和 Null,并将必要列加入下传的列需求集合中。 - -### Sort 和 TopN - -对于排序和 topN 算子,和 aggregation 算子裁剪的第四阶段类似,优化器会对于当前算子的 by 条件进行裁剪,去除常量和 Null,并将必要列加入下传的列需求集合中。 - -### Join - -相对其他算子,Join 算子的列裁剪比较特殊,因为 Join 算子的列是由 Join 的两个对象决定的。同时,由于其独特的性质, Join 算子本身的列裁剪不会对下层算子的列需求产生影响。因此,在优化器中,对于 Join 算子本身列的裁剪发生在下层算子的裁剪之后。具体步骤分为三步: - -第一步:优化器从 Join 的条件语句中提取出左右算子的列需求集合,并用其对于左右算子分别进行列裁剪。 - -第二步:优化器将左右算子裁剪后得到的列进行合并。 +出于上述考量,TiDB 会在逻辑优化阶段进行自上而下的扫描,裁剪不需要的列,减少资源浪费。该扫描过程称作 “列裁剪”,对应逻辑优化规则中的 `columnPruner`。如果要关闭这个规则,可以在参照[优化规则及表达式下推的黑名单](/blacklist-control-plan.md)中的关闭方法。 -第三步:优化器对于合并得到的列进行裁剪。 From e7ccb5e11a97977ddcf136f660c6eefaadffe68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Clawyer=E2=80=9Dlawyerphx=E2=80=9C?= Date: Thu, 21 May 2020 17:30:32 +0800 Subject: [PATCH 12/12] make Ci happy --- column-pruning.md | 1 - 1 file changed, 1 deletion(-) diff --git a/column-pruning.md b/column-pruning.md index a7a5626d79d1..e3db40df00e0 100644 --- a/column-pruning.md +++ b/column-pruning.md @@ -18,4 +18,3 @@ select a from t where b > 5 在该查询的过程中,t 表实际上只有 a, b 两列会被用到,而 c, d 的数据则显得多余。对应到该语句的查询计划,Selection 算子会用到 b 列,下面接着的 DataSource 算子会用到 a, b 两列,而剩下 c, d 两列则都可以裁剪掉,DataSource 算子在读数据时不需要将它们读进来。 出于上述考量,TiDB 会在逻辑优化阶段进行自上而下的扫描,裁剪不需要的列,减少资源浪费。该扫描过程称作 “列裁剪”,对应逻辑优化规则中的 `columnPruner`。如果要关闭这个规则,可以在参照[优化规则及表达式下推的黑名单](/blacklist-control-plan.md)中的关闭方法。 -