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)