Open mrdrivingduck opened 3 years ago
当数据量较大时,分区是一个不错的选择。现代数据库系统通常在不同的节点上存储或处理分区数据,从而获得并行。在一台机器上,分区数据可以按行水平划分,也可以按列垂直划分,从而减少查询时需要扫描的数据。
对于查询来说,优化器需要根据查询条件决定是否只扫描一部分分区,这被称为 静态分区消除。由于现在数据使用星型模式,静态分区消除不一定总是可以成功。有一些分区消除只能在运行时才能够被决定,这被称为 动态分区消除技术 - 大部分数据库要么不支持,要么支持得很差。另外还有无法在优化时确定分区键的场景,比如 prepared statement。
上述交代了问题。那么本文的贡献有:
PartitionSelector
和 DynamicScan
PartitionSelector
算子为了实现分区表扫描,引入了两个新的算子 PartitionSelector 和 DynamicScan。这两个运算符成对出现,被实现为 生产者-消费者 模式。PartitionSelector 计算需要扫描的分区的 OID,而 DynamicScan 根据这些 OID 进行扫描。两者通过共享内存进行通信。
如图,分区表的扫描有很多种模式:
对于 PartitionSelector 算子来说,其必备元素包含:
partScanId
标识符根据分区表 OID 和给出的分区列过滤条件,计算出所有符合条件的子分区,并把子分区的 OID push 到相同 partScanId
的 DynamicScan 中。PartitionSelector 最多只有一个孩子节点,并且输出与孩子节点一致 (如连接扫描所示,外节点)。
DynamicScan 负责消费所属 partScanId
的子分区 OID,并从相应分支中获取元组。
在计划树中,DynamicScan 的位置是确定的,重点是计算 PartitionSelector 的放置位置。通常可以有多个放置位置,但不是所有的放置都能达到最佳的分区消除效果。算法的输入是一个表达式树,上面只有 DynamicScan 算子而还没有任何 PartitionSelector 算子。PartitionSelector 的最优位置的定义是:扫描子分区的数量最少。另外,两者并不需要是同一个节点的直接后代。
主算法:
默认实现,用于无法进行分支过滤的算子,比如 GroupBy、Union、Project 等:
对于一个 select 计划树,判断相应的 part scan 是否定义在子树中:
对于连接来说:
如图,id 为 1 的 DynamicScan 出现在了 outer side 中,所以相应 PartitionSelector 也被下推到了 outer side 中;由于 outer side 中还有个 select,于是进一步下推,并把 select 的过滤条件加入到相应 PartitionSelector 中;最后,为了保证先后顺序,再加个 Sequence 算子。
而 id 为 2 的 DynamicScan 出现在了 inner side 中,而 join 条件可以被用于分支消除,所以 PartitionSelector 被下推到了 outer side,同时带上了连接过滤条件。这样 inner side 在扫描时就可以动态根据 outer side 中 PartitionSelector 指定的子分区进行扫描,从而实现分区消除。
将先前 PartitionSelector 的描述符定义进行修改,使其支持多个分区键和多个分区过滤表达式:
MPP 系统中有多种数据分布模式:
通过 Motion 算子来作为进程的执行边界。由于分区选择依赖于共享内存实现 PartitionSelector 和 DynamicScan 之间的通信,因此优化器需要保证一对 PartitionSelector 和 DynamicScan 算子在同一个进程内被执行,这意味着两者之间到公共祖先之间不能有 Motion 算子。
为啥?同一个进程还需要共享内存吗?应该是同一台机器吧。。。
经过对论文的理解,它的意思应该是被同属于一个 slice 的进程执行:
那么 ORCA 优化器具体是怎么干的呢?超出我能力范围了。。。
分区消除对于带有分区表的查询计划的性能来说至关重要,但在 MPP 架构中存在一些挑战。本文提出了一种能够生成在查询运行时决定访问哪些分区的查询计划,避免扫描不需要扫描的分区。
PDF:OptimizingQueriesOverPartitionedTablesInMPPSystems.pdf