全部產品
Search
文件中心

PolarDB:分區剪枝

更新時間:Aug 23, 2024

PolarDB PostgreSQL版(相容Oracle)支援分區剪枝功能,可以顯著提高對分區表的查詢速度。

概述

PolarDB PostgreSQL版(相容Oracle)提供了分區剪枝(Partition Pruning)功能,如果啟用了分區剪枝,規劃器將會檢查每個分區的定義並且檢驗該分區是否因為不包含符合查詢WHERE子句的行而無需掃描。若不包含,則會把分區從查詢計劃中排除(剪枝)。分區剪枝極大地減少了從磁碟檢索的資料量並縮短了處理時間,從而提高了查詢效能並最佳化了資源使用率。

根據實際的SQL語句,PolarDB PostgreSQL版(相容Oracle)資料庫支援使用靜態或動態剪枝。

  • 靜態剪枝發生在編譯時間,並預先訪問有關分區的資訊。靜態剪枝的一個樣本情境是包含WHERE條件的SQL語句,該條件在分區鍵列上帶有常量文字。

  • 動態剪枝發生在運行時,事先不知道語句要訪問的確切分區。動態剪枝的一個樣本是在WHERE條件中使用運算子或函數。

說明
  • 分區剪枝會影響發生剪枝的對象的統計資訊,也影響語句的執行計畫。

  • 分區剪枝技術將資料搜尋限制為僅搜尋您要搜尋的值可能所在的分區。 這兩種剪枝技術都會從查詢的執行計畫中刪除分區,從而提高效能。

  • 分區剪枝無法最佳化對子分區表的查詢,也無法最佳化分區於多個列上的RANGE分區表的查詢。

約束排除類似,分區剪枝只能對包含WHERE子句的查詢進行最佳化,且只有當WHERE子句中的限定符滿足某種格式時,才可進行最佳化。

參數

參數名稱

描述

polar_comp_enable_pruning

用於控制分區剪枝,取值範圍如下:

  • on(預設),表示啟用分區剪枝,快速剪去某些特定的查詢。

  • off,表示不啟用分區剪枝。

使用說明

按分區類型

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,也可以是更為複雜的包含運算子ANDBETWEEN的運算式,例如:

    WHERE size > 100 AND size <= 200 WHERE size BETWEEN 100 AND 200
    說明

    分區剪枝不支援包含ORIN運算子的運算式。

    樣本:

    ------指定一個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

CREATE TABLE sales
(
  dept_no     number,   
  part_no     varchar2,
  country     varchar2(20),
  date        date,
  amount      number
)
PARTITION BY LIST(country)
(
  PARTITION europe VALUES('FRANCE', 'ITALY'),
  PARTITION asia VALUES('INDIA', 'PAKISTAN'),
  PARTITION americas VALUES('US', 'CANADA')
);

------執行包含EXPLAIN語句的約束查詢,伺服器將只掃描表sales_asia,country值為INDIA的記錄將會儲存到該表中
EXPLAIN (COSTS OFF) SELECT * FROM sales WHERE country = 'INDIA';
                    QUERY PLAN                     
---------------------------------------------------
 Append
   ->  Seq Scan on sales
         Filter: ((country)::text = 'INDIA'::text)
   ->  Seq Scan on sales_asia
         Filter: ((country)::text = 'INDIA'::text)
(5 rows)

------執行如下查詢,搜尋未包括在分區鍵中的值匹配的記錄,伺服器將只掃描表sales_asia,country值為INDIA的記錄將會儲存到該表中
EXPLAIN (COSTS OFF) SELECT * FROM sales WHERE dept_no = '30'; 
               QUERY PLAN               
-----------------------------------------
 Append
   ->  Seq Scan on sales
         Filter: (dept_no = 30::numeric)
   ->  Seq Scan on sales_europe
         Filter: (dept_no = 30::numeric)
   ->  Seq Scan on sales_asia
         Filter: (dept_no = 30::numeric)
   ->  Seq Scan on sales_americas
         Filter: (dept_no = 30::numeric)
(9 rows)

約束排除在查詢子分區表時同樣適用:

CREATE TABLE sales
(
  dept_no     number,
  part_no     varchar2,
  country     varchar2(20),
  date        date,
  amount      number
)
PARTITION BY RANGE(date) SUBPARTITION BY LIST (country)
(
  PARTITION "2011" VALUES LESS THAN('01-JAN-2012')
  (
    SUBPARTITION europe_2011 VALUES ('ITALY', 'FRANCE'),
    SUBPARTITION asia_2011 VALUES ('PAKISTAN', 'INDIA'),
    SUBPARTITION americas_2011 VALUES ('US', 'CANADA')
  ),
  PARTITION "2012" VALUES LESS THAN('01-JAN-2013')
  (
    SUBPARTITION europe_2012 VALUES ('ITALY', 'FRANCE'),
    SUBPARTITION asia_2012 VALUES ('PAKISTAN', 'INDIA'),
    SUBPARTITION americas_2012 VALUES ('US', 'CANADA')
  ),
  PARTITION "2013" VALUES LESS THAN('01-JAN-2014')
  (
    SUBPARTITION europe_2013 VALUES ('ITALY', 'FRANCE'),
    SUBPARTITION asia_2013 VALUES ('PAKISTAN', 'INDIA'),
    SUBPARTITION americas_2013 VALUES ('US', 'CANADA')
  )
);

------當您查詢這張表時,查詢計劃器會從搜尋路徑中剪去任何可能不包含您想要的結果集的分區或子分區
EXPLAIN (COSTS OFF) SELECT * FROM sales WHERE country = 'US' AND date = 'Dec 12, 2012';
                                 QUERY PLAN                                 
-----------------------------------------------------------------------------
 Append
   ->  Seq Scan on sales
         Filter: (((country)::text = 'US'::text) AND (date = '12-DEC-12 00:00:00'::timestamp without time zone))
   ->  Seq Scan on sales_2012
         Filter: (((country)::text = 'US'::text) AND (date = '12-DEC-12 00:00:00'::timestamp without time zone))
   ->  Seq Scan on sales_americas_2012
         Filter: (((country)::text = 'US'::text) AND (date = '12-DEC-12 00:00:00'::timestamp without time zone))
(7 rows)

UPDATE、DELETE

CREATE TABLE t1_hash (id int , value int) PARTITION BY hash(id) partitions 4;

------update操作
EXPLAIN UPDATE t1_hash SET value = value+1 WHERE id = least(1,2);
                               QUERY PLAN                                
-------------------------------------------------------------------------
 Update on t1_hash  (cost=0.00..92.18 rows=24 width=14)
   Update on t1_hash_p1
   Update on t1_hash_p2 t1_hash
   ->  Append  (cost=0.00..92.18 rows=24 width=14)
         Subplans Removed: 1
         ->  Seq Scan on t1_hash_p1  (cost=0.00..46.03 rows=12 width=14)
               Filter: (id = LEAST(1, 2))
(7 rows)

------delete操作
EXPLAIN DELETE FROM t1_hash WHERE id = least(1,2);
                               QUERY PLAN                                
-------------------------------------------------------------------------
 Delete on t1_hash  (cost=0.00..92.12 rows=24 width=10)
   Delete on t1_hash_p1
   Delete on t1_hash_p2 t1_hash
   ->  Append  (cost=0.00..92.12 rows=24 width=10)
         Subplans Removed: 1
         ->  Seq Scan on t1_hash_p1  (cost=0.00..46.00 rows=12 width=10)
               Filter: (id = LEAST(1, 2))
(7 rows)

相關參考

約束排除

參數

參數名稱

描述

constraint_exclusion

用於控制約束排除,取值範圍如下:

  • partition(預設),表示啟用約束排除。

  • on,表示啟用約束排除。

  • off,表示不啟用約束排除。

當您啟用了約束排除,伺服器就會檢查定義給每個分區的約束來決定分區是否能滿足查詢。

  • 當執行不包含WHERE子句的SELECT語句時,查詢計劃會推薦用於搜尋整個表的執行計畫。

  • 當執行包含WHERE子句的SELECT語句時,查詢計劃會決定要儲存記錄的分區,並發送查詢片段給這個分區,然後從執行計畫中剪去不能包含記錄的分區。

如果您不使用分區表功能,則建議禁用約束排除,從而提高效能。

分區剪枝和約束排除的區別

分區剪枝和約束排除之間的區別在於:

  • 分區剪枝瞭解分區表中分區之間的關係。 約束排除則不然。

    例如,當查詢在列表分區表中搜尋特定值時,分區剪枝可能會導致只有特定分區可以儲存該值。 約束排除必須檢查為每個分區定義的約束。

  • 分區剪枝發生在最佳化器的早期,以減少規劃者必須考慮的分區數量。 約束排除發生在最佳化器的後期。