PolarDB PostgreSQL版(兼容Oracle)支持分区剪枝功能,可以显著提高对分区表的查询速度。
概述
PolarDB PostgreSQL版(兼容Oracle)提供了分区剪枝(Partition Pruning)功能,如果启用了分区剪枝,规划器将会检查每个分区的定义并且检验该分区是否因为不包含符合查询WHERE
子句的行而无需扫描。若不包含,则会把分区从查询计划中排除(剪枝)。分区剪枝极大地减少了从磁盘检索的数据量并缩短了处理时间,从而提高了查询性能并优化了资源利用率。
根据实际的SQL语句,PolarDB PostgreSQL版(兼容Oracle)数据库支持使用静态或动态剪枝。
静态剪枝发生在编译时,并预先访问有关分区的信息。静态剪枝的一个示例场景是包含WHERE条件的SQL语句,该条件在分区键列上带有常量文字。
动态剪枝发生在运行时,事先不知道语句要访问的确切分区。动态剪枝的一个示例是在WHERE条件中使用运算符或函数。
分区剪枝会影响发生剪枝的对象的统计信息,也影响语句的执行计划。
分区剪枝技术将数据搜索限制为仅搜索您要搜索的值可能所在的分区。 这两种剪枝技术都会从查询的执行计划中删除分区,从而提高性能。
分区剪枝无法优化对子分区表的查询,也无法优化分区于多个列上的RANGE分区表的查询。
和约束排除类似,分区剪枝只能对包含WHERE子句的查询进行优化,且只有当WHERE子句中的限定符满足某种格式时,才可进行优化。
参数
参数名称 | 描述 |
polar_comp_enable_pruning | 用于控制分区剪枝,取值范围如下:
|
使用说明
按分区类型
LIST分区表查询
对于LIST分区表的查询,分区剪枝能快速剪去包含WHERE子句的查询,运算符为等于(=)或IS NULL/IS NOT NULL。
示例:
------指定一个LIST分区表 CREATE TABLE sales_hist(..., country text, ...) PARTITION BY LIST(country) ( PARTITION americas VALUES('US', 'CA', 'MX'), PARTITION europe VALUES('BE', 'NL', 'FR'), PARTITION asia VALUES('JP', 'PK', 'CN'), PARTITION others VALUES(DEFAULT) ) ------在该LIST分区表的基础上,提取出包含如下WHERE子句的信息 WHERE country = 'US' WHERE country IS NULL;
通过第一个WHERE子句, 分区剪枝将排除分区europe、asia和others,因为这些分区不满足
WHERE country = 'US'
。通过第二个WHERE子句, 分区剪枝将排除分区americas、europe和asia。因为这些分区不满足
WHERE country IS NULL
。
RANGE分区表查询
对于RANGE分区表的查询,分区剪枝能快速剪去包含WHERE子句的查询。运算符可以是等于(=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=),或者是IS NULL/IS NOT NULL,也可以是更为复杂的包含运算符AND和 BETWEEN的表达式,例如:
WHERE size > 100 AND size <= 200 WHERE size BETWEEN 100 AND 200
说明分区剪枝不支持包含OR或IN运算符的表达式。
示例:
------指定一个RANGE分区表 CREATE TABLE boxes(id int, size int, color text) PARTITION BY RANGE(size) ( PARTITION small VALUES LESS THAN(100), PARTITION medium VALUES LESS THAN(200), PARTITION large VALUES LESS THAN(300) ) ------在该RANGE分区表的基础上,提取出包含如下WHERE子句的信息。 WHERE size > 100 -- 扫描medium和large分区 WHERE size >= 100 -- 扫描medium和large分区 WHERE size = 100 -- 扫描medium分区 WHERE size <= 100 -- 扫描small和medium分区 WHERE size < 100 -- 扫描small分区 WHERE size > 100 AND size < 199 -- 扫描medium分区 WHERE size BETWEEN 100 AND 199 -- 扫描medium分区 WHERE color = 'red' AND size = 100 -- 扫描medium分区 WHERE color = 'red' AND (size > 100 AND size < 199) -- 扫描medium分区
按不同阶段
PolarDB PostgreSQL版(兼容Oracle)中将条件表达式分为三个级别,即不变的(Immutable)、稳定的(Stable)、易变的(Volatile)。这三个级别依次对应了三种剪枝:
如果条件表达式值是不变的(比如常量静态值),则它会被用于最早的优化器剪枝;
如果条件表达式值是稳定的(比如
now()
),则会发生执行器初始阶段剪枝;如果条件表达式是易变的(比如
random()
),则会发生执行器运行时剪枝。
优化器剪枝
可以通过如下示例来了解优化器阶段的剪枝。
在PolarDB PostgreSQL版(兼容Oracle)中创建一个measurement
表,logdate
作为分区键,然后为其分别创建4个分区,即measurement_y2023q1
,measurement_y2023q2
, measurement_y2023q3
, measurement_y2023q4
四个分区,分别对应了2023年的四个季度。
CREATE TABLE measurement(
city_id int not null,
logdate date not null,
peaktemp int,
unitsales int
) PARTITION BY RANGE (logdate);
CREATE TABLE measurement_y2023q1 PARTITION OF measurement
FOR VALUES FROM ('2023-01-01') TO ('2023-04-01');
CREATE TABLE measurement_y2023q2 PARTITION OF measurement
FOR VALUES FROM ('2023-04-01') TO ('2023-07-01');
CREATE TABLE measurement_y2023q3 PARTITION OF measurement
FOR VALUES FROM ('2023-07-01') TO ('2023-10-01');
CREATE TABLE measurement_y2023q4 PARTITION OF measurement
FOR VALUES FROM ('2023-10-01') TO ('2024-04-01');
此时查询logdate
大于2023-10-01
的所有数据。使用EXPLAIN
可以看到,第一、二、三季度的数据默认都被剪枝掉了,不会执行查询,因为前三个分区的范围明显不满足logdate >= DATE '2023-10-01'
。这就是优化器阶段的分区剪枝,可以看到SQL中的限定条件是分区键logdate
,且条件表达式的值DATE '2023-10-01'
是静态的,或者是不可变的,它可以在优化器阶段计算出来。
EXPLAIN SELECT * FROM measurement WHERE logdate >= DATE '2023-10-01';
QUERY PLAN
-----------------------------------------------------------------------------
Append (cost=0.00..34.09 rows=567 width=20)
-> Seq Scan on measurement_y2023q4 (cost=0.00..31.25 rows=567 width=20)
Filter: (logdate >= '01-OCT-23 00:00:00'::date)
(3 rows)
执行器初始阶段剪枝
同样通过上文measurement
表来了解执行器初始剪枝。
如下可以看到,同样的measurement
表,同样的SQL查询,但是查询条件的表达式值从静态值变成了now()
,这是一个稳定的表达式,它不能在优化器阶段计算,但是可以在执行器初始阶段计算。假设今天是2023年7月,因此可以看到前两个季度分区被移除了Subplans Removed: 2
,只剩下了第三季度和第四季度。这就是执行器初始阶段的分区剪枝,可以看到SQL中的限定条件是分区键logdate
,且条件表达式的值now()
是稳定的,它可以在执行器初始阶段计算出来。
EXPLAIN SELECT * FROM measurement WHERE logdate >= now();
QUERY PLAN
-----------------------------------------------------------------------------
Append (cost=0.00..153.34 rows=2268 width=20)
Subplans Removed: 2
-> Seq Scan on measurement_y2023q3 (cost=0.00..35.50 rows=567 width=20)
Filter: (logdate >= now())
-> Seq Scan on measurement_y2023q4 (cost=0.00..35.50 rows=567 width=20)
Filter: (logdate >= now())
(6 rows)
执行器运行时剪枝
同样通过上文measurement
表来了解执行器运行时剪枝。
如下可以看到,同样的measurement表,同样的SQL查询,但是查询条件的表达式值从静态值变成了(select to_date('2023-10-1'))
,这是一个易变的子连接,它不能在优化器阶段计算,也不能在执行器初始阶段计算,只能在执行器运行时计算。
使用EXPLAIN ANALYZE
可以看出前三个季度的分区都是标记了(never executed)
,这就是执行器运行时剪枝。 它适用于易变的表达式值、子查询、子连接,以及join条件表达式。可以看到SQL中的限定条件是分区键logdate
,且条件表达式的值(select to_date('2023-10-1'))
是易变的子连接,它可以在执行器运行阶段计算出来。
EXPLAIN ANALYZE SELECT * FROM measurement WHERE logdate >= (select to_date('2023-10-1'));
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
Append (cost=0.01..136.35 rows=2268 width=20) (actual time=0.067..0.068 rows=0 loops=1)
InitPlan 1 (returns $0)
-> Result (cost=0.00..0.01 rows=1 width=8) (actual time=0.051..0.053 rows=1 loops=1)
-> Seq Scan on measurement_y2023q1 (cost=0.00..31.25 rows=567 width=20) (never executed)
Filter: (logdate >= $0)
-> Seq Scan on measurement_y2023q2 (cost=0.00..31.25 rows=567 width=20) (never executed)
Filter: (logdate >= $0)
-> Seq Scan on measurement_y2023q3 (cost=0.00..31.25 rows=567 width=20) (never executed)
Filter: (logdate >= $0)
-> Seq Scan on measurement_y2023q4 (cost=0.00..31.25 rows=567 width=20) (actual time=0.004..0.004 rows=0 loops=1)
Filter: (logdate >= $0)
示例
SELECT
UPDATE、DELETE
相关参考
约束排除
参数
参数名称 | 描述 |
constraint_exclusion | 用于控制约束排除,取值范围如下:
|
当您启用了约束排除,服务器就会检查定义给每个分区的约束来决定分区是否能满足查询。
当执行不包含WHERE子句的SELECT语句时,查询计划会推荐用于搜索整个表的执行计划。
当执行包含WHERE子句的SELECT语句时,查询计划会决定要存储记录的分区,并发送查询碎片给这个分区,然后从执行计划中剪去不能包含记录的分区。
如果您不使用分区表功能,则建议禁用约束排除,从而提高性能。
分区剪枝和约束排除的区别
分区剪枝和约束排除之间的区别在于:
分区剪枝了解分区表中分区之间的关系。 约束排除则不然。
例如,当查询在列表分区表中搜索特定值时,分区剪枝可能会导致只有特定分区可以保存该值。 约束排除必须检查为每个分区定义的约束。
分区剪枝发生在优化器的早期,以减少规划者必须考虑的分区数量。 约束排除发生在优化器的后期。