介紹
PostgreSQL分區表是邏輯上將一個大表通過表繼承方式划分為若干個子表。PostgreSQL V10.0版本之前僅支持表繼承方式分區表,V10版本之后支持聲明式分區,PostgreSQL支持通過表繼承來進行划分。每一個分區被創建為父表的一個子表。父表本身通常是空的,它的存在僅僅為了表示整個數據集。
聲明式與繼承式的區別:
- 對聲明式分區來說,分區必須具有和分區表正好相同的列集合,而在表繼承中,子表可以有父表中沒有出現過的額外列。
- 表繼承允許多繼承。
- 聲明式分區僅支持范圍、列表以及哈希分區,而表繼承允許數據按照用戶的選擇來划分(不過注意,如果約束排除不能有效地剪枝子表,查詢性能可能會很差)。
- 在使用聲明式分區時,一些操作比使用表繼承時要求更長的持鎖時間。例如,向分區表中增加分區或者從分區表移除分區要求在父表上取得一個ACCESS EXCLUSIVE鎖,而在常規繼承的情況下一個SHARE UPDATE EXCLUSIVE鎖就足夠了。
分區表有哪些優勢:
- 在某些情況下查詢性能能夠顯著提升,特別是當那些訪問壓力大的行在一個分區或者少數幾個分區時。划分可以取代索引的主導列、減小索引尺寸以及使索引中訪問壓力大的部分更有可能被放在內存中。
- 當查詢或更新訪問一個分區的大部分行時,可以通過該分區上的一個順序掃描來取代分散到整個表上的索引和隨機訪問,這樣可以改善性能。
- 如果需求計划使用划分設計,可以通過增加或移除分區來完成批量載入和刪除。ALTER TABLE NO INHERIT和DROP TABLE都遠快於一個批量操作。這些命令也完全避免了由批量DELETE造成的VACUUM負載。
- 很少使用的數據可以被遷移到便宜且較慢的存儲介質上。
什么時候使用表分區?
- PostgreSQL官方給出的建議:當表的尺寸超過了數據庫服務器物理內存時,划分會為表帶來好處。
在PostgreSQL中划分分區的幾種方式:
范圍划分:表被根據一個關鍵列或一組列划分為"范圍",不同的分區的范圍之間沒有重疊。例如,我們可以根據日期范圍划分,或者根據特定業務對象的標識符划分。
列表划分:通過顯式地列出每一個分區中出現的鍵值來划分表。
哈希分區:通過為每個分區指定模數和余數來對表進行分區。每個分區所持有的行都滿足:分區鍵的值除以為其指定的模數將產生為其指定的余數。
1 聲明式分區
1.1 介紹
PostgreSQL V10新增聲明式表分區,可以使用ATTACH PARTITION和DETACH PARTITION命令對分區進行管理。
個體分區在內部以繼承的方式鏈接到分區表,不過無法對聲明式分區表或其分區使用繼承的某些一般特性。例如,分區不能有除其所屬分區表之外的父表,一個常規表也不能從分區表繼承使得后者成為其父表。這意味着分區表及其分區不會參與到與常規表的繼承關系中。分區表及其分區組成的分區層次仍然是一種繼承層次。
聲明式分區表特性:
- 父表上的所有檢查約束和非空約束都將自動被它的后代所繼承,除非顯式地指定了NO INHERIT子句。其他類型的約束(唯一、主鍵和外鍵約束)則不會被繼承。
- 分區表的CHECK約束和NOT NULL約束總是會被其所有的分區所繼承。不允許在分區表上創建標記為NO INHERIT的CHECK約束。
- 只要分區表中不存在分區,則支持使用ONLY僅在分區表上增加或者刪除約束。一旦分區存在,那樣做就會導致錯誤,因為當分區存在時是不支持僅在分區表上增加或刪除約束的。不過,分區表本身上的約束可以被增加(如果它們不出現在父表中)和刪除。
- 由於分區表並不直接擁有任何數據,嘗試在分區表上使用TRUNCATE ONLY將總是返回錯誤。
- 分區不能有在父表中不存在的列。在使用CREATE TABLE創建分區時不能指定列,在事后使用ALTER TABLE時也不能為分區增加列。只有當表的列正好匹配父表時,才能使用ALTER TABLE ... ATTACH PARTITION將它作為分區加入。
- 如果NOT NULL約束在父表中存在,那么就不能刪除分區的列上的對應的NOT NULL約束。
分區表有下列限制:
- 沒有辦法創建跨越所有分區的排除約束,只可能單個約束每個葉子分區。
- 分區表上的惟一約束(也就是主鍵)必須包括所有分區鍵列。存在此限制是因為PostgreSQL只能每個分區中分別強制實施唯一性。
- BEFORE ROW 觸發器無法更改哪個分區是新行的最終目標。
- 不允許在同一個分區樹中混雜臨時關系和持久關系。因此,如果分區表是持久的,則其分區也必須是持久的,反之亦然。在使用臨時關系時,分區數的所有成員都必須來自於同一個會話。
1.2 創建聲明式分區
創建分區表並指定分區方式
create table log_history(id int not null,logdate date not null,num int) partition by range(logdate);
創建分區
create table log_history_2001 partition of log_history for values from ('2001-01-01') to ('2001-12-31');
create table log_history_2002 partition of log_history for values from ('2002-01-01') to ('2002-12-31');
create table log_history_2003 partition of log_history for values from ('2003-01-01') to ('2003-12-31');
create table log_history_2004 partition of log_history for values from ('2004-01-01') to ('2004-12-31');
create table log_history_2005 partition of log_history for values from ('2005-01-01') to ('2005-12-31');
create table log_history_2006 partition of log_history for values from ('2006-01-01') to ('2006-12-31');
create table log_history_2007 partition of log_history for values from ('2007-01-01') to ('2007-12-31');
create table log_history_2008 partition of log_history for values from ('2008-01-01') to ('2008-12-31');
create table log_history_2009 partition of log_history for values from ('2009-01-01') to ('2009-12-31');
create table log_history_2010 partition of log_history for values from ('2010-01-01') to ('2010-12-31');
create table log_history_2011 partition of log_history for values from ('2011-01-01') to ('2011-12-31');
create table log_history_2012 partition of log_history for values from ('2012-01-01') to ('2012-12-31');
create table log_history_2013 partition of log_history for values from ('2013-01-01') to ('2013-12-31');
create table log_history_2014 partition of log_history for values from ('2014-01-01') to ('2014-12-31');
create table log_history_2015 partition of log_history for values from ('2015-01-01') to ('2015-12-31');
create table log_history_2016 partition of log_history for values from ('2016-01-01') to ('2016-12-31');
create table log_history_2017 partition of log_history for values from ('2017-01-01') to ('2017-12-31');
create table log_history_2018 partition of log_history for values from ('2018-01-01') to ('2018-12-31');
create table log_history_2019 partition of log_history for values from ('2019-01-01') to ('2019-12-31');
create table log_history_2020 partition of log_history for values from ('2020-01-01') to ('2020-12-31');
創建分區並實現子分區,在創建分區的命令中指定partition by
create table log_history_2021 partition of log_history for values from ('2021-01-01') to ('2021-12-31') partition by range(logdate);
創建子分區
create table log_history_2021_01 partition of log_history_2021 for values from ('2021-01-01') to ('2021-03-31');
create table log_history_2021_02 partition of log_history_2021 for values from ('2021-04-01') to ('2021-06-30');
create table log_history_2021_03 partition of log_history_2021 for values from ('2021-07-01') to ('2021-08-31');
create table log_history_2021_04 partition of log_history_2021 for values from ('2021-09-01') to ('2021-12-31');
查看表定義查看分區
postgres=# \d+ log_history;
Partitioned table "public.log_history"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------+---------+-----------+----------+---------+---------+--------------+-------------
id | integer | | not null | | plain | |
logdate | date | | not null | | plain | |
num | integer | | | | plain | |
Partition key: RANGE (logdate)
Partitions: log_history_2001 FOR VALUES FROM ('2001-01-01') TO ('2001-12-31'),
log_history_2002 FOR VALUES FROM ('2002-01-01') TO ('2002-12-31'),
log_history_2003 FOR VALUES FROM ('2003-01-01') TO ('2003-12-31'),
log_history_2004 FOR VALUES FROM ('2004-01-01') TO ('2004-12-31'),
log_history_2005 FOR VALUES FROM ('2005-01-01') TO ('2005-12-31'),
log_history_2006 FOR VALUES FROM ('2006-01-01') TO ('2006-12-31'),
log_history_2007 FOR VALUES FROM ('2007-01-01') TO ('2007-12-31'),
log_history_2008 FOR VALUES FROM ('2008-01-01') TO ('2008-12-31'),
log_history_2009 FOR VALUES FROM ('2009-01-01') TO ('2009-12-31'),
log_history_2010 FOR VALUES FROM ('2010-01-01') TO ('2010-12-31'),
log_history_2011 FOR VALUES FROM ('2011-01-01') TO ('2011-12-31'),
log_history_2012 FOR VALUES FROM ('2012-01-01') TO ('2012-12-31'),
log_history_2013 FOR VALUES FROM ('2013-01-01') TO ('2013-12-31'),
log_history_2014 FOR VALUES FROM ('2014-01-01') TO ('2014-12-31'),
log_history_2015 FOR VALUES FROM ('2015-01-01') TO ('2015-12-31'),
log_history_2016 FOR VALUES FROM ('2016-01-01') TO ('2016-12-31'),
log_history_2017 FOR VALUES FROM ('2017-01-01') TO ('2017-12-31'),
log_history_2018 FOR VALUES FROM ('2018-01-01') TO ('2018-12-31'),
log_history_2019 FOR VALUES FROM ('2019-01-01') TO ('2019-12-31'),
log_history_2020 FOR VALUES FROM ('2020-01-01') TO ('2020-12-31'),
log_history_2021 FOR VALUES FROM ('2021-01-01') TO ('2021-12-31'), PARTITIONED
查看分區定義查看子分區
postgres=# \d+ log_history_2021;
Partitioned table "public.log_history_2021"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------+---------+-----------+----------+---------+---------+--------------+-------------
id | integer | | not null | | plain | |
logdate | date | | not null | | plain | |
num | integer | | | | plain | |
Partition of: log_history FOR VALUES FROM ('2021-01-01') TO ('2021-12-31')
Partition constraint: ((logdate IS NOT NULL) AND (logdate >= '2021-01-01'::date) AND (logdate < '2021-12-31'::date))
Partition key: RANGE (logdate)
Partitions: log_history_2021_01 FOR VALUES FROM ('2021-01-01') TO ('2021-03-31'),
log_history_2021_02 FOR VALUES FROM ('2021-04-01') TO ('2021-06-30'),
log_history_2021_03 FOR VALUES FROM ('2021-07-01') TO ('2021-08-31'),
log_history_2021_04 FOR VALUES FROM ('2021-09-01') TO ('2021-12-31')
1.3 聲明式分區維護
1.3.1 查看分區
查看有哪些分區表
postgres=# select partrelid::regclass,* from pg_partitioned_table;
partrelid | partrelid | partstrat | partnatts | partdefid | partattrs | partclass | partcollation | partexprs
------------------+-----------+-----------+-----------+-----------+-----------+-----------+---------------+-----------
log_history | 16399 | r | 1 | 0 | 2 | 3122 | 0 |
log_history_2001 | 16578 | r | 1 | 0 | 2 | 3122 | 0 |
log_history_2002 | 16577 | r | 1 | 0 | 2 | 3122 | 0 |
log_history_2003 | 16576 | r | 1 | 0 | 2 | 3122 | 0 |
log_history_2004 | 16575 | r | 1 | 0 | 2 | 3122 | 0 |
查看有哪些分區表(不查看子分區)
postgres=# \dP+
List of partitioned relations
Schema | Name | Owner | Type | Table | Total size | Description
--------+-------------+------------+-------------------+-------+------------+-------------
public | log_history | postgres12 | partitioned table | | 8192 bytes |
通過查看表定義查看分區
postgres=# \d+ log_history;
Partitioned table "public.log_history"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------+---------+-----------+----------+---------+---------+--------------+-------------
id | integer | | not null | | plain | |
logdate | date | | not null | | plain | |
num | integer | | | | plain | |
Partition key: RANGE (logdate)
Partitions: log_history_2001 FOR VALUES FROM ('2001-01-01') TO ('2001-12-31'),
log_history_2002 FOR VALUES FROM ('2002-01-01') TO ('2002-12-31'),
log_history_2003 FOR VALUES FROM ('2003-01-01') TO ('2003-12-31'),
log_history_2004 FOR VALUES FROM ('2004-01-01') TO ('2004-12-31'),
log_history_2005 FOR VALUES FROM ('2005-01-01') TO ('2005-12-31'),
log_history_2006 FOR VALUES FROM ('2006-01-01') TO ('2006-12-31'),
log_history_2007 FOR VALUES FROM ('2007-01-01') TO ('2007-12-31'),
log_history_2008 FOR VALUES FROM ('2008-01-01') TO ('2008-12-31'),
log_history_2009 FOR VALUES FROM ('2009-01-01') TO ('2009-12-31'),
log_history_2010 FOR VALUES FROM ('2010-01-01') TO ('2010-12-31'),
log_history_2011 FOR VALUES FROM ('2011-01-01') TO ('2011-12-31'),
log_history_2012 FOR VALUES FROM ('2012-01-01') TO ('2012-12-31'),
log_history_2013 FOR VALUES FROM ('2013-01-01') TO ('2013-12-31'),
log_history_2014 FOR VALUES FROM ('2014-01-01') TO ('2014-12-31'),
log_history_2015 FOR VALUES FROM ('2015-01-01') TO ('2015-12-31'),
log_history_2016 FOR VALUES FROM ('2016-01-01') TO ('2016-12-31'),
log_history_2017 FOR VALUES FROM ('2017-01-01') TO ('2017-12-31'),
log_history_2018 FOR VALUES FROM ('2018-01-01') TO ('2018-12-31'),
log_history_2019 FOR VALUES FROM ('2019-01-01') TO ('2019-12-31'),
log_history_2020 FOR VALUES FROM ('2020-01-01') TO ('2020-12-31'),
log_history_2021 FOR VALUES FROM ('2021-01-01') TO ('2021-12-31'), PARTITIONED
1.3.2 刪除分區
直接刪除子表刪除分區
drop table log_history_2001;
提示:刪除主表也會刪除所包含的分區。
使用DETACH將分區從分區表中移除,成為一個獨立的表
alter table log_history DETACH PARTITION log_history_2002;
drop table log_history_2002;
1.3.3 新增分區
新增分區
create table log_history_2001 partition of log_history for values from ('2001-01-01') to ('2001-12-31');
使用ATTACH添加分區
#創建表
postgres=# create table log_history_2002(like log_history including defaults including constraints);
CREATE TABLE
#增加check約束
postgres=# alter table log_history_2002 add check (logdate >= date'2002-01-01' and logdate < date'2003-01-01');
ALTER TABLE
postgres=# \d+ log_history_2002;
Table "public.log_history_2002"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------+---------+-----------+----------+---------+---------+--------------+-------------
id | integer | | not null | | plain | |
logdate | date | | not null | | plain | |
num | integer | | | | plain | |
Check constraints:
"log_history_2002_logdate_check" CHECK (logdate >= '2002-01-01'::date AND logdate < '2003-01-01'::date)
Access method: heap
#ATTACH添加PARTITION
postgres=# alter table log_history ATTACH PARTITION log_history_2002 for values from ('2002-01-01') to ('2002-12-31');
ALTER TABLE
1.4 enable_partition_pruning
enable_partition_pruning參數可以提升聲明式分區表性能的查詢優化技術。
select * from log_history where logdate<date'2010-09-11';
如果啟用enable_partition_pruning參數,上面的查詢將會掃描log_history表的每一個分區。如果啟用了此參數,規划器將會檢查每個分區的定義並且檢驗該分區是否因為不包含符合查詢WHERE子句的行而無需掃描。當規划器可以證實這一點時,它會把分區從查詢計划中排除。
啟用enable_partition_pruning(默認啟用此參數),查看執行計划,只掃描需要用到的分區
postgres=# set enable_partition_pruning = on;
SET
postgres=# explain select * from log_history where logdate<date'2010-09-11';
QUERY PLAN
--------------------------------------------------------------------------
Append (cost=0.00..389.00 rows=6800 width=12)
-> Seq Scan on log_history_2001 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2002 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2003 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2004 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2005 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2006 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2007 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2008 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2009 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2010 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
未啟用enable_partition_pruning,查看執行計划,會掃描全部分區
postgres=# set enable_partition_pruning = off;
SET
postgres=# explain select * from log_history where logdate<date'2010-09-11';
QUERY PLAN
--------------------------------------------------------------------------
Append (cost=0.00..778.00 rows=13600 width=12)
-> Seq Scan on log_history_2001 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2002 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2003 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2004 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2005 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2006 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2007 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2008 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2009 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2010 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2011 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2012 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2013 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2014 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2015 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2016 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2017 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2018 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2019 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
-> Seq Scan on log_history_2020 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2010-09-11'::date)
2 繼承式分區
2.1 介紹
聲明式與繼承式的區別:
- 對聲明式分區來說,分區必須具有和分區表正好相同的列集合,而在表繼承中,子表可以有父表中沒有出現過的額外列。
- 表繼承允許多繼承。
- 聲明式分區僅支持范圍、列表以及哈希分區,而表繼承允許數據按照用戶的選擇來划分(不過注意,如果約束排除不能有效地剪枝子表,查詢性能可能會很差)。
- 在使用聲明式分區時,一些操作比使用表繼承時要求更長的持鎖時間。例如,向分區表中增加分區或者從分區表移除分區要求在父表上取得一個ACCESS EXCLUSIVE鎖,而在常規繼承的情況下一個SHARE UPDATE EXCLUSIVE鎖就足夠了。
表繼承的特性:
- 繼承表自動繼承父表的約束,非空約束。但是不自動繼承uk、pk、fk、索引、存儲參數等。
- 一個表可以同時繼承多個父表,一個父表可以被多個子表繼承。注意:一個表繼承了多個主表的情況,共有字段上,所有的父表的約束包括not null的定義都必須繼承過來(同樣不包括pk、uk、fk等)。
- 查看主表默認情況下會連帶查詢所有的子表和自身。
2.2 創建繼承式分區
創建主表,所有的子表都從主表繼承,主表不包含數據,不在主表上定義任何檢查約束。
create table log_history(id int not null,logdate date not null,num int);
創建子表
create table log_history_2011(check (logdate >= date'2011-01-01' and logdate < date'2012-01-01')) inherits(log_history);
create table log_history_2012(check (logdate >= date'2012-01-01' and logdate < date'2013-01-01')) inherits(log_history);
create table log_history_2013(check (logdate >= date'2013-01-01' and logdate < date'2014-01-01')) inherits(log_history);
create table log_history_2014(check (logdate >= date'2014-01-01' and logdate < date'2015-01-01')) inherits(log_history);
create table log_history_2015(check (logdate >= date'2015-01-01' and logdate < date'2016-01-01')) inherits(log_history);
create table log_history_2016(check (logdate >= date'2016-01-01' and logdate < date'2017-01-01')) inherits(log_history);
create table log_history_2017(check (logdate >= date'2017-01-01' and logdate < date'2018-01-01')) inherits(log_history);
create table log_history_2018(check (logdate >= date'2018-01-01' and logdate < date'2019-01-01')) inherits(log_history);
create table log_history_2019(check (logdate >= date'2019-01-01' and logdate < date'2020-01-01')) inherits(log_history);
create table log_history_2020(check (logdate >= date'2020-01-01' and logdate < date'2021-01-01')) inherits(log_history);
create table log_history_2021(check (logdate >= date'2021-01-01' and logdate < date'2022-01-01')) inherits(log_history);
查看繼承分區
postgres=# select inhrelid::regclass,inhparent::regclass,inhseqno from pg_inherits;
inhrelid | inhparent | inhseqno
------------------+-------------+----------
log_history_2011 | log_history | 1
log_history_2012 | log_history | 1
log_history_2013 | log_history | 1
log_history_2014 | log_history | 1
log_history_2015 | log_history | 1
log_history_2016 | log_history | 1
log_history_2017 | log_history | 1
log_history_2018 | log_history | 1
log_history_2019 | log_history | 1
log_history_2020 | log_history | 1
log_history_2021 | log_history | 1
分區表創建后,往主表插入數據並不會將數據重定向到合適的分區,因此需要創建合適的觸發器函數來實現這一點。
postgres=# insert into log_history values(1,'2021-09-01',1);
INSERT 0 1
postgres=# select * from log_history;
id | logdate | num
----+------------+-----
1 | 2021-09-01 | 1
(1 row)
postgres=# select * from log_history_2021;
id | logdate | num
----+---------+-----
(0 rows)
創建函數
#vi log_history_insert_trigger.sql
CREATE OR REPLACE FUNCTION log_history_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF ( NEW.logdate >= DATE '2011-01-01' AND
NEW.logdate < DATE '2012-01-01' ) THEN
INSERT INTO log_history_2011 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2012-01-01' AND
NEW.logdate < DATE '2013-01-01' ) THEN
INSERT INTO log_history_2012 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2013-01-01' AND
NEW.logdate < DATE '2014-01-01' ) THEN
INSERT INTO log_history_2013 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2014-01-01' AND
NEW.logdate < DATE '2015-01-01' ) THEN
INSERT INTO log_history_2014 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2016-01-01' AND
NEW.logdate < DATE '2017-01-01' ) THEN
INSERT INTO log_history_2016 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2017-01-01' AND
NEW.logdate < DATE '2018-01-01' ) THEN
INSERT INTO log_history_2017 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2018-01-01' AND
NEW.logdate < DATE '2019-01-01' ) THEN
INSERT INTO log_history_2018 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2019-01-01' AND
NEW.logdate < DATE '2020-01-01' ) THEN
INSERT INTO log_history_2019 VALUES (NEW.*);
ELSIF ( NEW.logdate >= DATE '2021-01-01' AND
NEW.logdate < DATE '2022-01-01' ) THEN
INSERT INTO log_history_2021 VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'Date out of range. Fix the log_history_insert_trigger() function!';
END IF;
RETURN NULL;
END;
$$
LANGUAGE plpgsql;
創建函數
postgres=# \i log_history_insert_trigger.sql
創建主表索引(子表不會繼承主表的索引)
postgres=# create index index_log_history_logdate on log_history(logdate);
CREATE INDEX
postgres=# select * from pg_indexes where tablename like 'log_history%';
schemaname | tablename | indexname | tablespace | indexdef
------------+-------------+---------------------------+------------+------------------------------------------------------------------------------------
public | log_history | index_log_history_logdate | | CREATE INDEX index_log_history_logdate ON public.log_history USING btree (logdate)
創建分區索引
postgres=# create index index_log_history_2011_logdate on log_history_2011(logdate);
postgres=# create index index_log_history_2012_logdate on log_history_2012(logdate);
postgres=# create index index_log_history_2013_logdate on log_history_2013(logdate);
postgres=# create index index_log_history_2014_logdate on log_history_2014(logdate);
postgres=# create index index_log_history_2015_logdate on log_history_2015(logdate);
postgres=# create index index_log_history_2016_logdate on log_history_2016(logdate);
postgres=# create index index_log_history_2017_logdate on log_history_2017(logdate);
postgres=# create index index_log_history_2018_logdate on log_history_2018(logdate);
postgres=# create index index_log_history_2019_logdate on log_history_2019(logdate);
postgres=# create index index_log_history_2020_logdate on log_history_2020(logdate);
postgres=# create index index_log_history_2021_logdate on log_history_2021(logdate);
查看索引
postgres=# select * from pg_indexes where tablename like 'log_history%';
schemaname | tablename | indexname | tablespace | indexdef
------------+------------------+--------------------------------+------------+----------------------------------------------------------------------------------------------
public | log_history | index_log_history_logdate | | CREATE INDEX index_log_history_logdate ON public.log_history USING btree (logdate)
public | log_history_2011 | index_log_history_2011_logdate | | CREATE INDEX index_log_history_2011_logdate ON public.log_history_2011 USING btree (logdate)
public | log_history_2012 | index_log_history_2012_logdate | | CREATE INDEX index_log_history_2012_logdate ON public.log_history_2012 USING btree (logdate)
public | log_history_2013 | index_log_history_2013_logdate | | CREATE INDEX index_log_history_2013_logdate ON public.log_history_2013 USING btree (logdate)
public | log_history_2014 | index_log_history_2014_logdate | | CREATE INDEX index_log_history_2014_logdate ON public.log_history_2014 USING btree (logdate)
public | log_history_2015 | index_log_history_2015_logdate | | CREATE INDEX index_log_history_2015_logdate ON public.log_history_2015 USING btree (logdate)
public | log_history_2016 | index_log_history_2016_logdate | | CREATE INDEX index_log_history_2016_logdate ON public.log_history_2016 USING btree (logdate)
public | log_history_2017 | index_log_history_2017_logdate | | CREATE INDEX index_log_history_2017_logdate ON public.log_history_2017 USING btree (logdate)
public | log_history_2018 | index_log_history_2018_logdate | | CREATE INDEX index_log_history_2018_logdate ON public.log_history_2018 USING btree (logdate)
public | log_history_2019 | index_log_history_2019_logdate | | CREATE INDEX index_log_history_2019_logdate ON public.log_history_2019 USING btree (logdate)
public | log_history_2020 | index_log_history_2020_logdate | | CREATE INDEX index_log_history_2020_logdate ON public.log_history_2020 USING btree (logdate)
public | log_history_2021 | index_log_history_2021_logdate | | CREATE INDEX index_log_history_2021_logdate ON public.log_history_2021 USING btree (logdate)
2.3 繼承式分區維護
2.3.1 查看分區
使用\dp無法看到繼承式分區表,只能看到聲明式分區表
postgres=# \dP
List of partitioned relations
Schema | Name | Owner | Type | Table
--------+------+-------+------+-------
或者
postgres=# select partrelid::regclass,* from pg_partitioned_table;
partrelid | partrelid | partstrat | partnatts | partdefid | partattrs | partclass | partcollation | partexprs
-----------+-----------+-----------+-----------+-----------+-----------+-----------+---------------+-----------
查看繼承分區
postgres=# select inhrelid::regclass,inhparent::regclass,inhseqno from pg_inherits;
inhrelid | inhparent | inhseqno
------------------+-------------+----------
log_history_2011 | log_history | 1
log_history_2012 | log_history | 1
log_history_2013 | log_history | 1
log_history_2014 | log_history | 1
log_history_2015 | log_history | 1
log_history_2016 | log_history | 1
log_history_2017 | log_history | 1
log_history_2018 | log_history | 1
log_history_2019 | log_history | 1
log_history_2020 | log_history | 1
log_history_2021 | log_history | 1
2.3.2 刪除分區
方法1:直接刪除子表刪除分區
postgres=# drop table log_history_2011;
DROP TABLE
postgres=# select inhrelid::regclass,inhparent::regclass,inhseqno from pg_inherits;
inhrelid | inhparent | inhseqno
------------------+-------------+----------
log_history_2012 | log_history | 1
log_history_2013 | log_history | 1
log_history_2014 | log_history | 1
log_history_2015 | log_history | 1
log_history_2016 | log_history | 1
log_history_2017 | log_history | 1
log_history_2018 | log_history | 1
log_history_2019 | log_history | 1
log_history_2020 | log_history | 1
log_history_2021 | log_history | 1
方法2:通過繼承層次表刪除分區,分區會作為一張表
postgres=# alter table log_history_2012 NO INHERIT log_history;
ALTER TABLE
postgres=# select inhrelid::regclass,inhparent::regclass,inhseqno from pg_inherits;
inhrelid | inhparent | inhseqno
------------------+-------------+----------
log_history_2013 | log_history | 1
log_history_2014 | log_history | 1
log_history_2015 | log_history | 1
log_history_2016 | log_history | 1
log_history_2017 | log_history | 1
log_history_2018 | log_history | 1
log_history_2019 | log_history | 1
log_history_2020 | log_history | 1
log_history_2021 | log_history | 1
2.3.3 新增分區
方法1:添加分區,與創建原始子表一樣(然后更新log_history_insert_trigger函數)
postgres=# create table log_history_2022(check (logdate >= date'2022-01-01' and logdate < date'2023-01-01')) INHERITS (log_history);
CREATE TABLE
postgres=# select inhrelid::regclass,inhparent::regclass,inhseqno from pg_inherits;
inhrelid | inhparent | inhseqno
------------------+-------------+----------
log_history_2013 | log_history | 1
log_history_2014 | log_history | 1
log_history_2015 | log_history | 1
log_history_2016 | log_history | 1
log_history_2017 | log_history | 1
log_history_2018 | log_history | 1
log_history_2019 | log_history | 1
log_history_2020 | log_history | 1
log_history_2021 | log_history | 1
log_history_2022 | log_history | 1
方法2:創建新子表,然后將子表添加到分區中
創建子表
postgres=# create table log_history_2023 (like log_history including defaults including constraints);
CREATE TABLE
查看子表定義
postgres=# \d+ log_history_2023;
Table "public.log_history_2023"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------+---------+-----------+----------+---------+---------+--------------+-------------
id | integer | | not null | | plain | |
logdate | date | | not null | | plain | |
num | integer | | | | plain | |
Access method: heap
給子表添加CHECK約束
postgres=# alter table log_history_2023 add check(logdate >= '2023-01-01' and logdate < date'2024-01-01');
ALTER TABLE
查看子表定義
postgres=# \d+ log_history_2023;
Table "public.log_history_2023"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------+---------+-----------+----------+---------+---------+--------------+-------------
id | integer | | not null | | plain | |
logdate | date | | not null | | plain | |
num | integer | | | | plain | |
Check constraints:
"log_history_2023_logdate_check" CHECK (logdate >= '2023-01-01'::date AND logdate < '2024-01-01'::date)
Access method: heap
往子表中導入數據
\copy log_history_2023 from 'log_history_2023.dump';
繼承表方式添加分區
postgres=# alter table log_history_2023 INHERIT log_history;
ALTER TABLE
查看繼承分區信息
postgres=# select inhrelid::regclass,inhparent::regclass,inhseqno from pg_inherits;
inhrelid | inhparent | inhseqno
------------------+-------------+----------
log_history_2013 | log_history | 1
log_history_2014 | log_history | 1
log_history_2015 | log_history | 1
log_history_2016 | log_history | 1
log_history_2017 | log_history | 1
log_history_2018 | log_history | 1
log_history_2019 | log_history | 1
log_history_2020 | log_history | 1
log_history_2021 | log_history | 1
log_history_2022 | log_history | 1
log_history_2023 | log_history | 1
然后更新對應函數,添加2022、2023年CHECK約束
postgres=# \df+ log_history_insert_trigger;
List of functions
-[ RECORD 1 ]-------+----------------------------------------------------------------------------------------------
Schema | public
Name | log_history_insert_trigger
Result data type | trigger
Argument data types |
Type | func
Volatility | volatile
Parallel | unsafe
Owner | postgres12
Security | invoker
Access privileges |
Language | plpgsql
Source code | +
| BEGIN +
| IF ( NEW.logdate >= DATE '2011-01-01' AND +
| NEW.logdate < DATE '2012-01-01' ) THEN +
| INSERT INTO log_history_2011 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2012-01-01' AND +
| NEW.logdate < DATE '2013-01-01' ) THEN +
| INSERT INTO log_history_2012 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2013-01-01' AND +
| NEW.logdate < DATE '2014-01-01' ) THEN +
| INSERT INTO log_history_2013 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2014-01-01' AND +
| NEW.logdate < DATE '2015-01-01' ) THEN +
| INSERT INTO log_history_2014 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2016-01-01' AND +
| NEW.logdate < DATE '2017-01-01' ) THEN +
| INSERT INTO log_history_2016 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2017-01-01' AND +
| NEW.logdate < DATE '2018-01-01' ) THEN +
| INSERT INTO log_history_2017 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2018-01-01' AND +
| NEW.logdate < DATE '2019-01-01' ) THEN +
| INSERT INTO log_history_2018 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2019-01-01' AND +
| NEW.logdate < DATE '2020-01-01' ) THEN +
| INSERT INTO log_history_2019 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2021-01-01' AND +
| NEW.logdate < DATE '2022-01-01' ) THEN +
| INSERT INTO log_history_2021 VALUES (NEW.*); +
| ELSE +
| RAISE EXCEPTION 'Date out of range. Fix the log_history_insert_trigger() function!';+
| END IF; +
| RETURN NULL; +
| END; +
|
Description |
postgres=# \ef log_history_insert_trigger
postgres-# \g
CREATE FUNCTION
postgres=# \df+ log_history_insert_trigger;
List of functions
-[ RECORD 1 ]-------+----------------------------------------------------------------------------------------------
Schema | public
Name | log_history_insert_trigger
Result data type | trigger
Argument data types |
Type | func
Volatility | volatile
Parallel | unsafe
Owner | postgres12
Security | invoker
Access privileges |
Language | plpgsql
Source code | +
| BEGIN +
| IF ( NEW.logdate >= DATE '2011-01-01' AND +
| NEW.logdate < DATE '2012-01-01' ) THEN +
| INSERT INTO log_history_2011 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2012-01-01' AND +
| NEW.logdate < DATE '2013-01-01' ) THEN +
| INSERT INTO log_history_2012 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2013-01-01' AND +
| NEW.logdate < DATE '2014-01-01' ) THEN +
| INSERT INTO log_history_2013 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2014-01-01' AND +
| NEW.logdate < DATE '2015-01-01' ) THEN +
| INSERT INTO log_history_2014 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2016-01-01' AND +
| NEW.logdate < DATE '2017-01-01' ) THEN +
| INSERT INTO log_history_2016 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2017-01-01' AND +
| NEW.logdate < DATE '2018-01-01' ) THEN +
| INSERT INTO log_history_2017 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2018-01-01' AND +
| NEW.logdate < DATE '2019-01-01' ) THEN +
| INSERT INTO log_history_2018 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2019-01-01' AND +
| NEW.logdate < DATE '2020-01-01' ) THEN +
| INSERT INTO log_history_2019 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2021-01-01' AND +
| NEW.logdate < DATE '2022-01-01' ) THEN +
| INSERT INTO log_history_2021 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2022-01-01' AND +
| NEW.logdate < DATE '2023-01-01' ) THEN +
| INSERT INTO log_history_2022 VALUES (NEW.*); +
| ELSIF ( NEW.logdate >= DATE '2023-01-01' AND +
| NEW.logdate < DATE '2024-01-01' ) THEN +
| INSERT INTO log_history_2023 VALUES (NEW.*); +
| ELSE +
| RAISE EXCEPTION 'Date out of range. Fix the log_history_insert_trigger() function!';+
| END IF; +
| RETURN NULL; +
| END; +
|
Description |
2.4 constraint_exclusion
constraint_exclusion參數可以提升聲明式分區表性能的查詢優化技術。
select * from log_history where logdate<date'2015-09-11';
如果啟用constraint_exclusion參數,上面的查詢將會掃描log_history表的每一個分區。如果啟用了此參數,規划器將會檢查每個分區的定義並且檢驗該分區是否因為不包含符合查詢WHERE子句的行而無需掃描。當規划器可以證實這一點時,它會把分區從查詢計划中排除。
啟用constraint_exclusion = partition參數(默認啟用partition),指定計划只會掃描需要的分區
postgres=# set constraint_exclusion = partition;
SET
postgres=# explain select * from log_history where logdate<date'2015-09-11';
QUERY PLAN
--------------------------------------------------------------------------
Append (cost=0.00..198.39 rows=3468 width=12)
-> Seq Scan on log_history (cost=0.00..3.55 rows=68 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2011 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2012 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2013 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2014 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2015 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
如果未啟用constraint_exclusion參數,指定計划只會掃描需要的分區
postgres=# set constraint_exclusion = off;
SET
postgres=# explain select * from log_history where logdate<date'2015-09-11';
QUERY PLAN
--------------------------------------------------------------------------
Append (cost=0.00..431.79 rows=7548 width=12)
-> Seq Scan on log_history (cost=0.00..3.55 rows=68 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2011 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2012 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2013 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2014 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2015 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2016 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2017 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2018 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2019 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2020 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
-> Seq Scan on log_history_2021 (cost=0.00..35.50 rows=680 width=12)
Filter: (logdate < '2015-09-11'::date)
3 VACUUM或ANALYZE
VACUUM或ANALYZE tbl_partition只會對主表起作用,要想分析表,需要分別分析每個分區表。
查看分區表最后一次VACUUM和ANALYZE時間
postgres=# select relname,last_vacuum,last_analyze from pg_stat_all_tables where relname like 'log_history%';
relname | last_vacuum | last_analyze
------------------+-------------+--------------
log_history | |
log_history_2011 | |
log_history_2012 | |
log_history_2013 | |
log_history_2014 | |
log_history_2015 | |
log_history_2016 | |
log_history_2017 | |
log_history_2018 | |
log_history_2019 | |
log_history_2020 | |
log_history_2021 | |
vacuum主表只對主表起作用
postgres=# vacuum log_history;
VACUUM
postgres=# select relname,last_vacuum,last_analyze from pg_stat_all_tables where relname like 'log_history%';
relname | last_vacuum | last_analyze
------------------+-------------------------------+--------------
log_history | 2021-12-27 11:36:17.562586+08 |
log_history_2011 | |
log_history_2012 | |
log_history_2013 | |
log_history_2014 | |
log_history_2015 | |
log_history_2016 | |
log_history_2017 | |
log_history_2018 | |
log_history_2019 | |
log_history_2020 | |
log_history_2021 | |
vacuum主表和分區表
postgres=# vacuum log_history;
VACUUM
postgres=# vacuum log_history_2011;
VACUUM
postgres=# vacuum log_history_2012;
VACUUM
postgres=# vacuum log_history_2013;
VACUUM
postgres=# vacuum log_history_2014;
VACUUM
postgres=# vacuum log_history_2015;
VACUUM
postgres=# vacuum log_history_2016;
VACUUM
postgres=# vacuum log_history_2017;
VACUUM
postgres=# vacuum log_history_2018;
VACUUM
postgres=# vacuum log_history_2019;
VACUUM
postgres=# vacuum log_history_2020;
VACUUM
postgres=# vacuum log_history_2021;
VACUUM
postgres=# select relname,last_vacuum,last_analyze from pg_stat_all_tables where relname like 'log_history%';
relname | last_vacuum | last_analyze
------------------+-------------------------------+--------------
log_history | 2021-12-27 11:38:03.134217+08 |
log_history_2011 | 2021-12-27 11:38:03.134608+08 |
log_history_2012 | 2021-12-27 11:38:03.134919+08 |
log_history_2013 | 2021-12-27 11:38:03.135219+08 |
log_history_2014 | 2021-12-27 11:38:03.135507+08 |
log_history_2015 | 2021-12-27 11:38:03.135794+08 |
log_history_2016 | 2021-12-27 11:38:03.136088+08 |
log_history_2017 | 2021-12-27 11:38:03.136377+08 |
log_history_2018 | 2021-12-27 11:38:03.136668+08 |
log_history_2019 | 2021-12-27 11:38:03.136965+08 |
log_history_2020 | 2021-12-27 11:38:03.22314+08 |
log_history_2021 | 2021-12-27 11:38:03.435743+08 |