1. 程式人生 > >PostgreSQL LIST、RANGE 表分割槽方案

PostgreSQL LIST、RANGE 表分割槽方案

簡 介

PG分割槽:就是把邏輯上的一個大表分割成物理上的幾塊。

分割槽的優點

    1. 某些型別的查詢效能得到提升
    2. 更新的效能也可以得到提升,因為某塊的索引要比在整個資料集上的索引要小。
    3. 批量刪除可以通過簡單的刪除某個分割槽來實現。
    4. 可以將很少用的資料移動到便宜的、轉速慢的儲存介質上。

分割槽實現原理

10.x版本之前PG表分割槽的實現原理:PG中是通過表的繼承來實現的,建立一個主表,裡面是空的,然後每個分割槽去繼承它。無論何時,該主表裡面都必須是空的

官網建議:只有當表本身大小超過了機器實體記憶體的實際大小時,才考慮分割槽。

原分割槽用法

以繼承表的方式實現:

create table tbl( a int, b varchar(10) ); 
create table tbl_1 ( check ( a <= 1000 ) ) INHERITS (tbl); 
create table tbl_2 ( check ( a <= 10000 and a >1000 ) ) INHERITS (tbl);
create table tbl_3 ( check ( a <= 100000 and a >10000 ) ) INHERITS (tbl);

再通過建立觸發器或者規則,實現資料分發,只需要向子表插入資料則會自動分配到子表中

CREATE
OR REPLACE FUNCTION tbl_part_tg() RETURNS TRIGGER AS $$ BEGIN IF ( NEW. a <= 1000 ) THEN INSERT INTO tbl_1 VALUES (NEW.*); ELSIF ( NEW. a > 1000 and NEW.a <= 10000 ) THEN INSERT INTO tbl_2 VALUES (NEW.*); ELSIF ( NEW. a > 10000 and NEW.a <= 100000 ) THEN INSERT INTO tbl_3 VALUES (NEW.*); ELSIF ( NEW
. a > 100000 and NEW.a <= 1000000 ) THEN INSERT INTO tbl_4 VALUES (NEW.*); ELSE RAISE EXCEPTION 'data out of range!'; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER insert_tbl_part_tg BEFORE INSERT ON tbl FOR EACH ROW EXECUTE PROCEDURE tbl_part_tg();

分割槽建立成功

如何實現分割槽過濾?

對於分割槽表來說,如果有50個分割槽表,對於某個條件的值如果能確定,那麼很可能直接過濾掉49個分割槽,大大提高掃描速度,當然分割槽表也能放在不同的物理盤上,提高IO速度。

對於查詢是怎麼實現分割槽表過濾呢?
約束排除 是否使用約束排除通過postgresql.conf中引數constraint_exclusion 來控制,
只有三個值

 constraint_exclusion = on
 on:所有情況都會進行約束排除檢查
 off:關閉,所有約束都不生效
 partition:對分割槽表或者繼承表進行約束排查,預設為partition

如:

select *from tbl where a = 12345;

首先找到主表tbl,然後通過tbl找到它的子表,找到後再對再拿著謂詞條件a = 12345對一個個子表約束進行檢查,不符合條件表就去掉不掃描,實現分割槽表過濾,下面簡單介紹下約束排除原始碼邏輯。

如何實現資料分發?

基於規則的話,會在查詢重寫階段按時替換規則生成新的插入語句,基於觸發器會在insert主表前觸發另外一個insert操作,這兩個邏輯都比較簡單,相關程式碼不再介紹。

錯誤描述:在新建分割槽主表時提示以下錯誤資訊
這裡寫圖片描述

錯誤原因:在本地postgresql.conf 配置了 search_path = ‘$user’ ,所以在使用的時候需要先建立當前使用者對應的schema,如果不存在,則會提示錯誤

解決方法:在建立表時指定建立的schemal,即可成功。
這裡寫圖片描述

PostgreSQL 10.x LIST分割槽方案

postgres=# CREATE TABLE list_parted (
postgres(# a int
postgres(# ) PARTITION BY LIST (a);
CREATE TABLE
postgres=# CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN (1);
CREATE TABLE
postgres=# CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
CREATE TABLE
postgres=# CREATE TABLE part_3 PARTITION OF list_parted FOR VALUES IN (3);
CREATE TABLE
postgres=# CREATE TABLE part_4 PARTITION OF list_parted FOR VALUES IN (4);
CREATE TABLE
postgres=# CREATE TABLE part_5 PARTITION OF list_parted FOR VALUES IN (5);
CREATE TABLE
postgres=#
postgres=# insert into list_parted values(32); --faled
ERROR:  no partition of relation "list_parted" found for row
DETAIL:  Failing row contains (32).
postgres=# insert into part_1 values(1);
INSERT 0 1
postgres=# insert into part_1 values(2);--faled
ERROR:  new row for relation "part_1" violates partition constraint
DETAIL:  Failing row contains (2).
postgres=# explain select *from list_parted where a =1;
                           QUERY PLAN
-----------------------------------------------------------------
 Append  (cost=0.00..41.88 rows=14 width=4)
   ->  Seq Scan on list_parted  (cost=0.00..0.00 rows=1 width=4)
         Filter: (a = 1)
   ->  Seq Scan on part_1  (cost=0.00..41.88 rows=13 width=4)
         Filter: (a = 1)
(5 rows)

上面是LIST分割槽表,建表是先建主表,再建子表,子表以 PARTITION OF 方式說明和主表關係,約束條件應該就是後面的in裡面。

Explain 執行sql解析計劃

cost:資料庫自定義的消耗單位,通過統計資訊來估計SQL消耗。(查詢分析是根據analyze的固執生成的,生成之後按照這個查詢計劃執行,執行過程中analyze是不會變的。所以如果估值和真是情況差別較大,就會影響查詢計劃的生成。)
rows:根據統計資訊估計SQL返回結果集的行數。
width:返回結果集每一行的長度,這個長度值是根據pg_statistic表中的統計資訊來計算的。

pgAdminList分割槽目錄

PostgreSQL 10.x RANGE分割槽

建立RANGE分割槽

postgres=# CREATE TABLE range_parted (
postgres(# a int
postgres(# ) PARTITION BY RANGE (a);
CREATE TABLE
postgres=# CREATE TABLE range_parted1 PARTITION OF range_parted FOR VALUES from (1) TO (1000);
CREATE TABLE
postgres=# CREATE TABLE range_parted2 PARTITION OF range_parted FOR VALUES FROM (1000) TO (10000);
CREATE TABLE
postgres=# CREATE TABLE range_parted3 PARTITION OF range_parted FOR VALUES FROM (10000) TO (100000);
CREATE TABLE
postgres=#
postgres=# insert into range_parted1 values(343);
INSERT 0 1
postgres=#
postgres=# explain select *from range_parted where a=32425;
                             QUERY PLAN
---------------------------------------------------------------------
 Append  (cost=0.00..41.88 rows=14 width=4)
   ->  Seq Scan on range_parted  (cost=0.00..0.00 rows=1 width=4)
         Filter: (a = 32425)
   ->  Seq Scan on range_parted3  (cost=0.00..41.88 rows=13 width=4)
         Filter: (a = 32425)
(5 rows)
postgres=# set constraint_exclusion = off;
SET
postgres=# explain select *from range_parted where a=32425;
                             QUERY PLAN
---------------------------------------------------------------------
 Append  (cost=0.00..125.63 rows=40 width=4)
   ->  Seq Scan on range_parted  (cost=0.00..0.00 rows=1 width=4)
         Filter: (a = 32425)
   ->  Seq Scan on range_parted1  (cost=0.00..41.88 rows=13 width=4)
         Filter: (a = 32425)
   ->  Seq Scan on range_parted2  (cost=0.00..41.88 rows=13 width=4)
         Filter: (a = 32425)
   ->  Seq Scan on range_parted3  (cost=0.00..41.88 rows=13 width=4)
         Filter: (a = 32425)
(9 rows)

上述操作中的 a的取值範圍為【0,1000)即插入值若為1000邊界值,則會儲存在第二個分
區表中和LIST差不多,就是語法略有不同,範圍表值是一個連續的範圍,LIST表是單點或多
點的集合。從上面例子可以看到,顯然還是走的約束排除過濾子表的方式。

constraint_exclusion = “on ,off,partition ”; 該引數為postgresql.conf中的引數
    on 表示所有的查詢都會執行約束排除
    off 關閉,所有的查詢都不會執行約束排除
    partition :表示只對分割槽的表進行約束排除

分割槽列的型別必須支援btree索引介面(幾乎涵蓋所有型別, 後面會說到檢查方法)。
更新後的資料如果超出了所在分割槽的範圍,則會報錯

RANGE分割槽修改資料出錯

PostgreSQL 分割槽注意事項

語法

1、建立主表

[ PARTITION BY { RANGE | LIST } ( { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [, ... ] ) ] 

2、建立分割槽

PARTITION OF parent_table [ (  
  { column_name [ column_constraint [ ... ] ]  
    | table_constraint }  
    [, ... ]  
) ] FOR VALUES partition_bound_spec  
and partition_bound_spec is:  
{ IN ( expression [, ...] )   -- list分割槽  
  |  
  FROM ( { expression | UNBOUNDED } [, ...] ) TO ( { expression | UNBOUNDED } [, ...] ) }  -- range分割槽, unbounded表示無限小或無限大

語法解釋

partition by 指定分割槽表的型別range或list
指定分割槽列,或表示式作為分割槽鍵。
range分割槽表鍵:支援指定多列、或多表達式,支援混合(鍵,非表示式中的列,會自動
新增not null的約束)
list分割槽表鍵:支援單個列、或單個表示式

分割槽鍵必須有對應的btree索引方法的ops(可以檢視系統表得到)

select typname from pg_type where oid in (select opcintype from pg_opclass);    
  • 主表不會有任何資料,資料會根據分割槽規則進入對應的分割槽表

  • 如果插入資料時,分割槽鍵的值沒有匹配的分割槽,會報錯

  • 不支援全域性的unique, primary key, exclude, foreign key約束,只能在對應的分割槽建立這些約束

  • 分割槽表和主表的 列數量,定義 必須完全一致,(包括OID也必須一致,要麼都有,要麼都沒有)

  • 可以為分割槽表的列單獨增加Default值,或約束。

  • 使用者還可以對分割槽表增加表級約束

  • 如果新增的分割槽表check約束,名字與主表的約束名一致,則約束內容必須與主表一致

  • 當用戶往主表插入資料庫時,記錄被自動路由到對應的分割槽,如果沒有合適的分割槽,則報錯

  • 如果更新資料,並且更新後的KEY導致資料需要移動到另一分割槽,則會報錯,(意思是分割槽鍵 可以更新,但是不支援更新後的資料移出到別的分割槽表)

  • 修改主表的欄位名,欄位型別時,會自動同時修改所有的分割槽

  • TRUNCATE 主表時,會清除所有繼承表分割槽的記錄(如果有多級分割槽,則會一直清除到所有的直接和間接繼承的分割槽)

  • 如果要清除單個分割槽,請對分割槽進行操作

  • 如果要刪除分割槽表,可以使用DROP TABLE的DDL語句,注意這個操作會對主表也加access exclusive lock。