[TOC] # 分区裁剪 当用户访问分区表时,往往只需要访问其中部分的分区,所以通过优化器避免访问无关分区的优化过程我们称之为分区裁剪((Partition Pruning)。分区裁剪是分区表提供的重要优化手段,通过分区的裁剪,SQL 的执行效率可以得到大幅度的提升。您可以利用分区裁剪的特性,在访问中加入定位分区的条件,避免访问无关数据,优化查询性能。 分区裁剪本身是一个比较复杂的过程,优化器需要根据用户表的分区信息和 SQL 中给定的条件,抽取出相关的分区信息。由于 SQL 中的条件往往比较复杂,整个抽取逻辑的复杂性也随之增加,这一过程由 OceanBase 数据库中的 Query Range 子模块完成。 以下示例中当用户使用如下 SQL 访问分区表时,由于 c1 为 1 的数据全部处于第 1 号分区(p1),实际上我们只需要访问该分区即可(避免访问第 0、2、3、4 号分区): ~~~ obclient> CREATE TABLE t1 ( c1 INT, c2 INT ) PARTITION BY HASH(c1) partitions 5; obclient> SELECT * FROM t1 WHERE c1 = 1; ~~~ 通过`EXPLAIN`查看执行计划可以看到分区裁剪的结果: ~~~ obclient> EXPLAIN SELECT * FROM t1 WHERE c1 = 1 \G *************************** 1. row *************************** Query Plan: =================================== |ID|OPERATOR |NAME|EST. ROWS|COST| ----------------------------------- |0 |TABLE SCAN|t1 |1 |1303| =================================== Outputs & filters: ------------------------------------- 0 - output([t1.c1], [t1.c2]), filter([t1.c1 = 1]), access([t1.c1], [t1.c2]), partitions(p1) ~~~ ## 一级分区裁剪的基本原理 **Hash/List 分区** 分区裁剪就是根据`where`子句里面的条件并且计算得到分区列的值,然后通过结果判断需要访问哪些分区。如果分区函数为表达式,且该表达式作为一个整体出现在等值条件里,也可以做分区裁剪。 示例如下: ~~~ obclient> CREATE TABLE t1 ( c1 INT, c2 INT ) PARTITION BY HASH(c1 + c2) partitions 5; obclient> EXPLAIN SELECT * FROM t1 WHERE c1 + c2 = 1 \G *************************** 1. row *************************** Query Plan: =================================== |ID|OPERATOR |NAME|EST. ROWS|COST| ----------------------------------- |0 |TABLE SCAN|t1 |5 |1303| =================================== Outputs & filters: ------------------------------------- 0 - output([t1.c1], [t1.c2]), filter([t1.c1 + t1.c2 = 1]), access([t1.c1], [t1.c2]), partitions(p1) ~~~ **Range 分区** 通过`where`子句的分区键的范围跟表定义的分区范围的交集来确定需要访问的分区。对于 Range 分区,因为考虑到函数的单调性,如果分区表达式是一个函数并且查询条件是一个范围,则不支持分区裁剪。 下述示例中,分区条件为表达式,而查询条件为非等值条件(c1 100),则无法进行分区裁剪: ~~~ obclient> CREATE TABLE t1 ( c1 INT, c2 INT ) PARTITION BY RANGE(c1 + 1) ( PARTITION p0 VALUES less than (100), PARTITION p1 VALUES less than (200) ); obclient> EXPLAIN SELECT * FROM t1 WHERE c1 < 150 and c1 > 110 \G *************************** 1. row *************************** Query Plan: ============================================ |ID|OPERATOR |NAME|EST. ROWS|COST| -------------------------------------------- |0 |EXCHANGE IN DISTR | |19 |1410| |1 | EXCHANGE OUT DISTR| |19 |1303| |2 | TABLE SCAN |t1 |19 |1303| ============================================ Outputs & filters: ------------------------------------- 0 - output([t1.c1], [t1.c2]), filter(nil) 1 - output([t1.c1], [t1.c2]), filter(nil) 2 - output([t1.c1], [t1.c2]), filter([t1.c1 < 150], [t1.c1 > 110]), access([t1.c1], [t1.c2]), partitions(p[0-1]) ~~~ 如果查询条件是等值条件,则可以进行分区裁剪。示例如下: ~~~ obclient> EXPLAIN SELECT * FROM t1 WHERE c1 = 150 \G *************************** 1. row *************************** Query Plan: =================================== |ID|OPERATOR |NAME|EST. ROWS|COST| ----------------------------------- |0 |TABLE SCAN|t1 |1 |1303| =================================== Outputs & filters: ------------------------------------- 0 - output([t1.c1], [t1.c2]), filter([t1.c1 = 150]), access([t1.c1], [t1.c2]), partitions(p1) ~~~ ## 二级分区裁剪的基本原理 对于二级分区,先按照一级分区键确定一级需要访问的分区,然后在通过二级分区键确定二级分区需要访问的分区。然后做一个乘积确定二级分区访问的所有物理分区。 以下示例中,经过计算得到一级分区裁剪结果是 p1、p2,而二级分区裁剪的结果是 sp1,所以访问的物理分区为 p1sp1 和 p2sp1: ~~~ obclient> CREATE TABLE t1 ( c1 INT , c2 INT ) PARTITION BY hash(c1) SUBPARTITION BY RANGE(c2) SUBPARTITION template ( SUBPARTITION sp0 VALUES less than(100), SUBPARTITION sp1 VALUES less than(200) ) partitions 5 SELECT * FROM t1 WHERE (c1 = 1 OR c1 = 2) AND (c2 > 101 AND c2 < 150) obclient> EXPLAIN SELECT * FROM t1 WHERE (c1 = 1 or c1 = 2) and (c2 > 101 and c2 < 150) \G *************************** 1. row *************************** Query Plan: ============================================ |ID|OPERATOR |NAME|EST. ROWS|COST| -------------------------------------------- |0 |EXCHANGE IN DISTR | |1 |1403| |1 |EXCHANGE OUT DISTR| |1 |1303| |2 | TABLE SCAN |t1 |1 |1303| ============================================ Outputs & filters: ------------------------------------- 0 - output([t1.c1], [t1.c2]), filter(nil) 1 - output([t1.c1], [t1.c2]), filter(nil) 2 - output([t1.c1], [t1.c2]), filter([t1.c1 = 1 OR t1.c1 = 2], [t1.c2 > 101], [t1.c2 < 150]), access([t1.c1], [t1.c2]), partitions(p1sp1, p2sp1) ~~~ 某些场景下,分区裁剪可能会存在一定程度的放大,但优化器可以确保裁剪的结果是所需访问数据的超集,不会存在丢失数据的情况。