通过分区裁剪功能(Partition Pruning)可以避免访问无关分区,使 SQL 的执行效率得到大幅度提升。本文主要介绍了分区裁剪的原理和应用。
当用户访问分区表时,往往只需要访问其中的部分分区,通过优化器避免访问无关分区的优化过程我们称之为分区裁剪(Partition Pruning)。分区裁剪是分区表提供的重要优化手段,通过分区裁剪,SQL 的执行效率可以得到大幅度提升。您可以利用分区裁剪的特性,在访问中加入定位分区的条件,避免访问无关数据,优化查询性能。
分区裁剪本身是一个比较复杂的过程,优化器需要根据用户表的分区信息和 SQL 中给定的条件,抽取出相关的分区信息。由于 SQL 中的条件往往比较复杂,整个抽取逻辑的复杂性也随之增加,这一过程由 OceanBase 数据库中的 Query Range 子模块完成。
当用户访问分区表时,由于 col1 为 1 的数据全部处于第 1 号分区(p1),所以我们只需要访问该分区,而无需访问第 0、2、3、4 号分区。如下所示:
obclient> CREATE TABLE tbl1(col1 INT,col2 INT) PARTITION BY HASH(col1) PARTITIONS 5;
obclient> SELECT * FROM tbl1 WHERE col1 = 1;
通过 EXPLAIN 查看执行计划可以看到分区裁剪的结果:
obclient> EXPLAIN SELECT * FROM tbl1 WHERE col1 = 1 \G
*************************** 1. row ***************************
Query Plan: ===================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
-----------------------------------
|0 |TABLE SCAN|TBL1|990 |383 |
===================================
Outputs & filters:
-------------------------------------
0 - output([TBL1.COL1], [TBL1.COL2]), filter(nil),
access([TBL1.COL1], [TBL1.COL2]), partitions(p1)
1 row in set
一级分区裁剪的基本原理
Hash/List 分区
分区裁剪就是根据 where 子句里面的条件并且计算得到分区列的值,然后通过结果判断需要访问哪些分区。如果分区条件为表达式,且该表达式作为一个整体出现在等值条件里,也可以做分区裁剪。
分区条件为表达式 c1 + c2,且该表达式作为一个整体出现在等值条件里的分区裁剪的示例如下:
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 + 1,而查询条件为非等值条件 c1 < 150 and 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)
二级分区裁剪的基本原理
对于二级分区,先按照一级分区键确定一级分区需要访问的分区,再通过二级分区键确定二级分区需要访问的分区。最后做一个乘积确定二级分区需要访问的所有物理分区。
以下示例中,经过计算得到一级分区裁剪结果是 p0,而二级分区裁剪的结果是 sp0,所以访问的物理分区为 p0sp0。
obclient> CREATE TABLE tbl2_rr(col1 INT,col2 INT)
PARTITION BY RANGE(col1)
SUBPARTITION BY RANGE(col2)
SUBPARTITION TEMPLATE
(SUBPARTITION sp0 VALUES LESS THAN(1000),
SUBPARTITION sp1 VALUES LESS THAN(2000)
)
(PARTITION p0 VALUES LESS THAN(100),
PARTITION p1 VALUES LESS THAN(200)
) ;
Query OK, 0 rows affected
obclient> EXPLAIN SELECT * FROM tbl2_rr
WHERE (col1 = 1 or col1 = 2) and (col2 > 101 and col2 < 150) \G
*************************** 1. row ***************************
Query Plan: ======================================
|ID|OPERATOR |NAME |EST. ROWS|COST|
--------------------------------------
|0 |TABLE SCAN|TBL2_RR|99 |53 |
======================================
Outputs & filters:
-------------------------------------
0 - output([TBL2_RR.COL1], [TBL2_RR.COL2]), filter(nil),
access([TBL2_RR.COL1], [TBL2_RR.COL2]), partitions(p0sp0)
1 row in set
某些场景下,分区裁剪可能会存在一定程度的放大,但优化器可以确保裁剪的结果是所需访问数据的超集,不会存在丢失数据的情况。




