PolarDB PostgreSQL版(兼容Oracle)支持分區剪枝功能,可以顯著提高對分區表的查詢速度。
概述
PolarDB PostgreSQL版(兼容Oracle)提供了分區剪枝(Partition Pruning)功能,如果啟用了分區剪枝,規劃器將會檢查每個分區的定義并且檢驗該分區是否因為不包含符合查詢WHERE
子句的行而無需掃描。若不包含,則會把分區從查詢計劃中排除(剪枝)。分區剪枝極大地減少了從磁盤檢索的數據量并縮短了處理時間,從而提高了查詢性能并優化了資源利用率。
根據實際的SQL語句,PolarDB PostgreSQL版(兼容Oracle)數據庫支持使用靜態或動態剪枝。
靜態剪枝發生在編譯時,并預先訪問有關分區的信息。靜態剪枝的一個示例場景是包含WHERE條件的SQL語句,該條件在分區鍵列上帶有常量文字。
動態剪枝發生在運行時,事先不知道語句要訪問的確切分區。動態剪枝的一個示例是在WHERE條件中使用運算符或函數。
分區剪枝會影響發生剪枝的對象的統計信息,也會影響語句的執行計劃。
分區剪枝技術將數據搜索限制為僅搜索您要搜索的值可能所在的分區。 這兩種剪枝技術都會從查詢的執行計劃中刪除分區,從而提高性能。
分區剪枝和約束排除的區別
分區剪枝和約束排除之間的區別在于:
分區剪枝了解分區表中分區之間的關系。 約束排除則不然。
例如,當查詢在列表分區表中搜索特定值時,分區剪枝可能會導致只有特定分區可以保存該值。 約束排除必須檢查為每個分區定義的約束。
分區剪枝發生在優化器的早期,以減少規劃者必須考慮的分區數量。 約束排除發生在優化器的后期。
不同階段的分區剪枝
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)