Postgresql杂谈 10—Postgresql中的分区表
一、關于分區表
? ? ? ?表分區是在大數據優化中的一種常見的分表方案,通過將大數據按照一定的規則(最常見的是按照時間)進行分表處理,將邏輯上的一個大表分割成物理上的幾塊表,插入數據時,數據會自動插入到不同的分區表中,從而實現查詢或者其它操作的性能優化。相比于一個大表,分區表具有以下優點:
(1)當查詢或者更新一個分區的大部分記錄時,采用順序掃描而不是隨機掃描,可以獲得巨大的性能提升。
(2)使用不頻繁的歷史數據可以轉移到一些低廉的存儲介質上,而熱數據放到性能較好的存儲介質上,可以最大限度的減少成本。
(3)位于同一個分區的熱數據可以全部緩存到內存中,查詢hit的幾率更高,可以加快查詢速度。
? ? ? ?Postgresql中有兩種建立分區表的方法——使用聲明式分區和表繼承分區,接下來,筆者就這兩種方式分別進行介紹。
二、聲明式分區
? ? ? ?聲明式分區的意思就是Postgresql提供了相應的DDL語句創建分區表,使用聲明式的方法建立分區表的方法比較簡單,下面,我們就使用一個例子來介紹這種創建分區表的方法。
? ? ? ?假設,我們創建一個operate_log表,用來記錄用戶的操作記錄。因為操作記錄會隨著時間的累積,越來越大,所以我們選擇建立一個分區表,并按照時間分區,每一個一個分區。
stock_analysis_data=# create table operate_log (id int,user_id int,operate_type int,content text,operate_date date) partition by range(operate_date); CREATE TABLE? ? ? ?注意partition by range這段語句,是在指明使用哪個字段進行分區。現在,我們只是創建了分區表的基礎表,分區表目前是還沒有創建,基礎表是無法插入數據的。此時,向基礎表里面插入數據,會報下面的錯誤:
stock_analysis_data=# insert into operate_log values(1,1,1,'用戶注冊','2021-06-24'); ERROR: no partition of relation "operate_log" found for row DETAIL: Partition key of the failing row contains (operate_date) = (2021-06-24). stock_analysis_data=#? ? ? ?接下來,我們來創建該基礎表的分區表:
stock_analysis_data=# create table operater_log_202106 partition of operate_log for values from ('2021-06-01') to ('2021-07-01'); CREATE TABLE? ? ? ?可以看到,創建分區表最關鍵的兩個語句是:
(1)partition of —— 指定待分區的基礎表是哪個
(2)for values from () to () —— 指定當前分區表的分區范圍
? ? ? ?現在,我們可以向基礎表里面插入數據,它可以自動的插入到相應的分區中:
stock_analysis_data=# insert into operate_log values(1,1,1,'用戶注冊','2021-06-24'); INSERT 0 1 stock_analysis_data=# select * from operater_log_202106;id | user_id | operate_type | content | operate_date ----+---------+--------------+----------+--------------1 | 1 | 1 | 用戶注冊 | 2021-06-24 (1 row)? ? ? ?從基礎表里面直接select,可以查詢到分區表里面的數據:
stock_analysis_data=# select * from operate_log;id | user_id | operate_type | content | operate_date ----+---------+--------------+----------+--------------1 | 1 | 1 | 用戶注冊 | 2021-06-24 (1 row)? ? ? ?但是,如果插入定義在分區范圍之外的數據,就會報錯:
stock_analysis_data=# insert into operate_log values(1,1,1,'用戶注冊','2021-07-24'); ERROR: no partition of relation "operate_log" found for row DETAIL: Partition key of the failing row contains (operate_date) = (2021-07-24). stock_analysis_data=#三、表繼承方式實現分區表
? ? ? ?Postgresql中所謂的表繼承,就是我們在創建一個新表時,可以繼承基礎表,新表具有基礎表的所有字段。而向新表中插入數據時,通過基礎表也可以查詢到這些數據。實際上,使用聲明式方式創建分區表,內部實現就是利用表繼承。我們還是利用operate_log表作為基礎表,利用表繼承的方式創建分區表。不過在此之前,我們需要先把原來的operate_log表drop掉,因為通過表繼承的方式實現的分區表,基礎表必須是普通表,而之前的operate_log表我們創建時聲明成了partition。
stock_analysis_data=# drop table operate_log; DROP TABLE stock_analysis_data=# create table operate_log (id int,user_id int,operate_type int,content text,operate_date date); CREATE TABLE? ? ? ?下面,以新的operate_log為基礎表,進行分區表的創建:
stock_analysis_data=# create table operate_log_202105(check(operate_date>='2021-05-01' and operate_date<'2020-06-01')) inherits (operate_log); CREATE TABLE stock_analysis_data=# create table operate_log_202106(check(operate_date>='2021-06-01' and operate_date<'2020-07-01')) inherits (operate_log); CREATE TABLE? ? ? ?可以看到,以表繼承的方式創建分區表時需要使用inherits關鍵字顯式的指明基礎表,而且需要自己寫檢查約束來約束分區字段的取值范圍。僅僅完成上述工作之后,你會發現,現在的分區表實際上還無法使用,我們向基礎表里面插入數據時,并不能自動的插入到分區表中。
stock_analysis_data=# insert into operate_log values(1,1,1,'用戶注冊','2021-06-24'); INSERT 0 1? ? ? ?上述命令,向基礎表operate_log里面插入了一條數據,按理來說,應該插入到operate_log_202106表中,但是實際上,并沒有插入進去:
stock_analysis_data=# select * from operate_log_202106;id | user_id | operate_type | content | operate_date ----+---------+--------------+---------+-------------- (0 rows)? ? ? ?原因是我們使用表繼承的方式實現分區表時,需要自己去控制向主表插入數據時,數據自動插入分區表的邏輯。目前主要有兩種方式可以實現這一邏輯:一種是使用觸發器,另一種是使用自定義Rule。接下來,筆者分別進行介紹。
3.1 使用觸發器進行數據自動插入分區表
? ? ? ?我們先創建觸發器函數:
create or replace function operate_log_insert_trigger() returns trigger as $$beginif (NEW.operate_date >= date'2021-06-01' and NEW.operate_date < date'2021-07-01') theninsert into operate_log_202106 values (NEW.*);elsif(NEW.operate_date >= date'2021-05-01' and NEW.operate_date < date'2021-06-01') theninsert into operate_log_202105 values (NEW.*);elseraise exception 'out of range!!!';end if; return null;end; $$ language plpgsql;? ? ? ?然后創建觸發器:
create trigger insert_sale_detail_trigger before insert on operate_log for each row execute procedure operate_log_insert_trigger(); CREATE TRIGGER? ? ? ?接下來,向基礎表中插入數據:
insert into operate_log values (2,2,1,'用戶退出',date'2021-06-24');? ? ? ?再從operate_log_202106表中查詢:
stock_analysis_data=# select * from operate_log_202106;id | user_id | operate_type | content | operate_date ----+---------+--------------+----------+--------------2 | 2 | 1 | 用戶退出 | 2021-06-24 (1 row)3.2 使用自定義Rule進行數據自動插入分區表
? ? ? ?以上是使用觸發器實現的自動從基礎表中向分區表插入數據,接下來,筆者介紹下使用自定義Rule向分區表中插入數據的方法。示例如下:
create rule operate_log_insert_202106 as on insert to operate_log where (operate_date>=date'2021-06-01' and operate_date<date'2021-07-01') do insteadinsert into operate_log values (NEW.*);create rule operate_log_insert_202105 as on insert to operate_log where (operate_date>=date'2021-05-01' and operate_date<date'2021-06-01') do insteadinsert into operate_log values (NEW.*);? ? ? ?創建好上述規則之后,同樣可以實現向基礎表插入數據時自動插入到相應的分區表,但是和觸發器相比,自定義的Rule有它自己的特點:
(1) 每次插入數據都要檢查,找到合適的規則然后用insert語句替代原來的insert語句,開銷明顯比觸發器要大。
(2) 創建的Rule不定義會一定觸發,在COPY插入數據時就不能觸發Rule,但是觸發器可以正常使用
(3) 當插入的數據超過了定義的范圍時,觸發器會報錯,但是Rule會直接插入到基礎表里。
(4) 不管是觸發器還是Rule,當我們需要擴展分區表時,都不得不修改觸發器函數或者新建Rule。
四、約束排除
? ? ? ?約束排除是種優化分區表性能的查詢方法,簡單說來就是:在從基礎表進行查詢時,如果where條件包含在某個分區表約束條件之內時。如果打開約束排除,則直接從改分區表進行查詢,但是如果關閉約束排除,則遍歷所有分區表查找。在postgresql.conf文件中可以設置約束排除開啟或是關閉:constraint_exclusion = partition (默認開啟),設置off關閉。
? ? ? ?在約束排除開啟的情況下,我們用explain查看查詢情況:
stock_analysis_data=# explain (analyze,verbose,costs,buffers,timing) select count(*) from operate_log where operate_date >= date'2021-06-01';QUERY PLAN -----------------------------------------------------------------------------------------------------------------------------------Aggregate (cost=26.05..26.06 rows=1 width=8) (actual time=0.023..0.025 rows=1 loops=1)Output: count(*)Buffers: shared hit=1-> Append (cost=0.00..25.16 rows=357 width=0) (actual time=0.013..0.016 rows=1 loops=1)Buffers: shared hit=1-> Seq Scan on public.operater_log_202106 (cost=0.00..23.38 rows=357 width=0) (actual time=0.010..0.013 rows=1 loops=1)Filter: (operater_log_202106.operate_date >= '2021-06-01'::date)Buffers: shared hit=1Planning Time: 0.193 msExecution Time: 0.065 ms (10 rows)? ? ? ?關閉約束排除,找到/var/lib/pgsql/11/data下的postgresql.conf文件,修改:
constraint_exclusion = off? ? ? ?重啟數據庫服務后,再explain上述查詢語句(或者直接執行set constraint_exclusion = off,設置本次session中的參數):
stock_analysis_data=# explain (analyze,verbose,costs,buffers,timing) select count(*) from operate_log where operate_date >= date'2021-06-01';QUERY PLAN -----------------------------------------------------------------------------------------------------------------------------------Aggregate (cost=78.16..78.17 rows=1 width=8) (actual time=0.029..0.030 rows=1 loops=1)Output: count(*)Buffers: shared hit=3-> Append (cost=0.00..75.48 rows=1071 width=0) (actual time=0.010..0.022 rows=3 loops=1)Buffers: shared hit=3-> Seq Scan on public.operater_log_202104 (cost=0.00..23.38 rows=357 width=0) (actual time=0.008..0.010 rows=1 loops=1)Filter: (operater_log_202104.operate_date >= '2021-06-01'::date)Buffers: shared hit=1-> Seq Scan on public.operater_log_202105 (cost=0.00..23.38 rows=357 width=0) (actual time=0.003..0.004 rows=1 loops=1)Filter: (operater_log_202105.operate_date >= '2021-06-01'::date)Buffers: shared hit=1-> Seq Scan on public.operater_log_202106 (cost=0.00..23.38 rows=357 width=0) (actual time=0.003..0.004 rows=1 loops=1)Filter: (operater_log_202106.operate_date >= '2021-06-01'::date)Buffers: shared hit=1Planning Time: 0.120 msExecution Time: 0.076 ms (16 rows)五、總結
? ? ? ?根據本文的內容,我們可以得到如下結論:
(1)利用表分區可以實現將一個大表化整為零,數據按照日期或者其它分表規則劃分到分區表上,一方面可以加快查詢和更新效率,另一方面可以將不同熱度的數據分布到不同的存儲介質上,減少部署成本。
(2)表分區可以使用聲明式分區或者表繼承分區,前者的內部實現實際上也是利用表繼承分區,但是前者使用較為簡單,建議使用聲明式的分區方式。
(3)使用分區表時,要開啟約束排除,設置postgresql.conf文件中的constraint_exclusion = partition(默認值)。
總結
以上是生活随笔為你收集整理的Postgresql杂谈 10—Postgresql中的分区表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何做抖音GIF表情包
- 下一篇: 厦门新车上牌经验分享