PG-分區表


分區表

早在 10 版本之前 PostgreSQL 分區表一般通過繼承加觸發器方式實現,稱為傳統分區表。

PostgreSQL 10 版本提供的分區表稱為內置分區表。

傳統分區表

傳統分區表是通過繼承和觸發器方式實現的, 其實現過程步驟多, 非常復雜,需要定義父表、定義子表、 定義子表約束 、 創建子表索引 、 創建分區插入、 刪除 、 修改函數和觸發器等, 可以說是在普通表基礎上手工實現的分區表。繼承是傳統分區表的重要組成部分。

繼承表

首先定義一張父表,然后創建子表並繼承父表

示例
創建一張日志模型表 tbl_log
create table tbl_log(id int4, create_date date, log_type text);
創建一張子表tbl_log_sql用於存儲 SQL 日志
create table tbl_log_sql(sql text) INHERITS(tbl_log);
-- 通過INHERITS(tbl_log)關鍵字表示tbl_log_sql繼承表tbl_log,子表可以定義額外的字段
查看表結構
\d tbl_log_sql
DML操作
  • 父表和子表分別插入記錄
INSERT INTO tbl_log VALUES (1,'2021-10-10', null);
INSERT INTO tbl_log_sql VALUES (2,'2021-10-10', null,'select 1;');
  • 查詢

    select * from tbl_log; -- 查詢父表 tbl_log 會顯示兩條的記錄,但子表的字段不會顯示
    select * from tbl_log_sql;
    
    select p.relname, c.* 
      from tbl_log c, pg_class p
    where c.tableoid = p.oid;
    
    • 若只查詢父表記錄,需要在父表名稱前加上ONLY關鍵字

      select * from only tbl_log;
      
  • 注意:對於使用了繼承表的場景,對父表的update, delete的操作需謹慎,它會對父表和子表的數據進行DML操作

創建分區表

步驟
  1. 創建父表
  2. 通過 INHERITS 方式創建繼承表,也稱之為子表或分區
  3. 給子表創建約束
  4. 給子表創建索引,繼承操作不會繼承父表上的索引
  5. 在父表上定義 INSERT 、 DELETE 、 UPDATE 觸發器,將 SQL 分發到對應分區(可選)
  6. 啟用 constraint_exclusion 參數
具體實現

創建一張范圍分區表,並且定義年月子表存儲月數據

創建父表
CREATE TABLE log_ins (id serial,
user_id int4,
create_time timestamp(O) without time zone);
創建子表
CREATE TABLE log_ins_history(CHECK ( create_time < '2021-01-01')) INHERITS (log_ins);
CREATE TABLE log_ins_202101(CHECK ( create_time >= '2021-01-01' and create_time < '2021-02-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202102(CHECK ( create_time >= '2021-02-01' and create_time < '2021-03-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202103(CHECK ( create_time >= '2021-03-01' and create_time < '2021-04-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202104(CHECK ( create_time >= '2021-04-01' and create_time < '2021-05-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202105(CHECK ( create_time >= '2021-05-01' and create_time < '2021-06-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202106(CHECK ( create_time >= '2021-06-01' and create_time < '2021-07-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202107(CHECK ( create_time >= '2021-07-01' and create_time < '2021-08-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202108(CHECK ( create_time >= '2021-08-01' and create_time < '2021-09-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202109(CHECK ( create_time >= '2021-09-01' and create_time < '2021-10-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202110(CHECK ( create_time >= '2021-10-01' and create_time < '2021-11-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202111(CHECK ( create_time >= '2021-11-01' and create_time < '2021-12-01')) INHERITS(log_ins);
CREATE TABLE log_ins_202112(CHECK ( create_time >= '2021-12-01' and create_time < '2022-01-01')) INHERITS(log_ins);
創建子表索引
CREATE INDEX idx_his_ctime ON log_ins_history USING btree (create_time);
CREATE INDEX idx_log_his_202101_ctime ON log_ins_202101 USING btree (create_time);
CREATE INDEX idx_log_his_202102_ctime ON log_ins_202102 USING btree (create_time);
CREATE INDEX idx_log_his_202103_ctime ON log_ins_202103 USING btree (create_time);
CREATE INDEX idx_log_his_202104_ctime ON log_ins_202104 USING btree (create_time);
CREATE INDEX idx_log_his_202105_ctime ON log_ins_202105 USING btree (create_time);
CREATE INDEX idx_log_his_202106_ctime ON log_ins_202106 USING btree (create_time);
CREATE INDEX idx_log_his_202107_ctime ON log_ins_202107 USING btree (create_time);
CREATE INDEX idx_log_his_202108_ctime ON log_ins_202108 USING btree (create_time);
CREATE INDEX idx_log_his_202109_ctime ON log_ins_202109 USING btree (create_time);
CREATE INDEX idx_log_his_202110_ctime ON log_ins_202110 USING btree (create_time);
CREATE INDEX idx_log_his_202111_ctime ON log_ins_202111 USING btree (create_time);
CREATE INDEX idx_log_his_202112_ctime ON log_ins_202112 USING btree (create_time);
創建觸發器
  • insert
-- 創建觸發器函數,設置數據插入父表時的路由規則
create or replace function log_hit_insert_trigger()
  returns trigger
  language plpgsql
as $function$
begin
  if (NEW.create_time < '2021-01-01') then
    insert into log_ins_history values (NEW.*);
  elsif (NEW.create_time >= '2021-01-01' and NEW.create_time < '2021-02-01') then
    insert into log_ins_202101 values (NEW.*);
  elsif (NEW.create_time >= '2021-02-01' and NEW.create_time < '2021-03-01') then
    insert into log_ins_202102 values (NEW.*);
  elsif (NEW.create_time >= '2021-03-01' and NEW.create_time < '2021-04-01') then
    insert into log_ins_202103 values (NEW.*);
  elsif (NEW.create_time >= '2021-04-01' and NEW.create_time < '2021-05-01') then
    insert into log_ins_202104 values (NEW.*);
  elsif (NEW.create_time >= '2021-05-01' and NEW.create_time < '2021-06-01') then
    insert into log_ins_202105 values (NEW.*);
  elsif (NEW.create_time >= '2021-06-01' and NEW.create_time < '2021-07-01') then
    insert into log_ins_202106 values (NEW.*);
  elsif (NEW.create_time >= '2021-07-01' and NEW.create_time < '2021-08-01') then
    insert into log_ins_202107 values (NEW.*);
  elsif (NEW.create_time >= '2021-08-01' and NEW.create_time < '2021-09-01') then
    insert into log_ins_202108 values (NEW.*);
  elsif (NEW.create_time >= '2021-09-01' and NEW.create_time < '2021-10-01') then
    insert into log_ins_202109 values (NEW.*);
  elsif (NEW.create_time >= '2021-10-01' and NEW.create_time < '2021-11-01') then
    insert into log_ins_202110 values (NEW.*);
  elsif (NEW.create_time >= '2021-11-01' and NEW.create_time < '2021-12-01') then
    insert into log_ins_202111 values (NEW.*);
  elsif (NEW.create_time >= '2021-12-01' and NEW.create_time < '2022-01-01') then
    insert into log_ins_202112 values (NEW.*);
  else
    raise exception 'create_time out of range.';
  end if;
  return NULL;
END;
$function$;

-- 創建觸發器
CREATE TRIGGER insert_log_ins_trigger BEFORE INSERT ON log_ins FOR EACH ROW
EXECUTE PROCEDURE log_hit_insert_trigger();
  • delete
  • update

使用分區表

插入數據

往父表 log_ins 插入測試數據,並驗證數據是否插入對應分區

INSERT INTO log_ins(user_id,create_time)
SELECT round(100000000*random()),generate_series('2020-12-01'::date,
'2021-12-01'::date , '1 minute');
查詢數據
SELECT count(*) FROM log_ins;
SELECT count(*) FROM ONLY log_ins;

SELECT * FROM ONLY log_ins limit 2;

SELECT min(create_time),max(create_time) FROM log_ins_202101;
-- 查看子表大小
\dt+ log_ins*

EXPLAIN ANALYZE SELECT * FROM log_ins_202101 WHERE create_time > '2021-01-01' AND create time < '2021-02-01';

constraint_exclusion 參數

用來控制優化器是否根據表上的約束來優化查詢,參數值:

  • on :所有表都通過約束優化查詢
  • off:所有表都不通過約束優化查詢
  • partition :只對繼承表和 UNION ALL 子查詢通過檢索約束來優化查詢

如果設置成 on 或 partition ,查詢父表時優化器會根據子表上的約束判斷檢索哪些子表,而不需要掃描所有子表,從而提升查詢性能。

-- 會話級別設置參數
set constraint_exclusion = off;
EXPLAIN ANALYZE SELECT * FROM log_ins_202101 WHERE create_time > '2021-01-01' AND create time < '2021-02-01';

添加分區

創建分區
CREATE TABLE log_ins_202201(LIKE log_ins INCLUDING ALL);
添加約束
ALTER TABLE log_ins_202201 ADD CONSTRAINT log_ins_202201_create_time check
CHECK ( create_time >= '2022-01-01' AND create_time < '2022-02-01');
刷新觸發器函數

新分區 log_ins_202101 繼承到父表 log_ins
ALTER TABLE log_ins_202201 INHERIT log_ins;

刪除分區

1. 直接刪除分區方式
DROP TABLE log_ins_202201;
2. 先將分區的繼承關系去掉后刪除
ALTER TABLE log_ins_202201 NO INHERIT log_ins;
DROP TABLE log_ins_202201;

查看分區信息

\d+ log_ins

SELECT
  nmsp_parent.nspname as parent_schema,
  parent.relname as parent,
  nmsp_child.nspname as child_schema,
  child.relname as child
from
  pg_inherits join pg_class parent on pg_inherits.inhparent = parent.oid 
  join pg_class child on pg_inherits.inhrelid = child.oid 
  join pg_namespace nmsp_parent on nmsp_parent.oid = parent.relnamespace 
  join pg_namespace nmsp_child on nmsp_clild.oid = child.relnamespace
where parent.relname = 'log_ins';

-- 分區表的分區數量
select 
  nspname,
  relname,
  count(*) as partition_num
from
  pg_class c,
  pg_namespace n,
  pg_inherits i
where c.oid = i.inhparent
  and c.relnamespace = n.oid
  and c.relhassubclass
  and c.relkind in ('r','p')
group by 1,2 
order by partition_num desc;

內置分區表

PostgreSQL10 一個重量級新特性是支持內置分區表,用戶不需要預先在父表上定義INSERT 、 DELETE 、 UPDATE 觸發器,對父表的 DML 操作會自動路由到相應分區,相比傳統分區表大幅度降低了維護成本,PostgreSQL10目前僅支持范圍分區和列表分區。

內置分區表實際與傳統分區表一樣,都是用繼承的方式實現。

語法

創建分區

創建內置分區表步驟

  • 創建父表,指定分區鍵和分區策略 。
  • 創建分區,創建分區時須指定分區表的父表和分區鍵的取值范圍,注意分區鍵的范圍不要有重疊,否則會報錯 。
  • 在分區上創建相應索引,通常情況下分區鍵上的索引 是必須的,非分區鍵的索引可根據實際應用場景選擇是否創建 。

示例

創建內置分區表過程
-- 1. 創建范圍分區表
CREATE TABLE log_par (
	id serial,
    user_id int4,
    create_time timestamp(0) without time zone
) PARTITION BY RANGE(create_time);

-- 2. 創建分區
create table log_par_his partition of log_par for values from ('1970-01-01') to ('2020-01-01');
create table log_par_202001 partition of log_par for values from ('2020-01-01') to ('2020-02-01');
create table log_par_202002 partition of log_par for values from ('2020-02-01') to ('2020-03-01');

-- 3. 創建分區索引
create index idx_log_par_his_ctime on log_par_his using btree(create_time);
create index idx_log_par_202001_ctime on log_par_202001 using btree(create_time);
create index idx_log_par_202002_ctime on log_par_202002 using btree(create_time);

使用分區
-- 1. 插入數據
insert into log_par(user_id, create_time)
select round(1000000*random()), generate_series('2019-12-01'::date, '2020-02-28'::date, '1 minute');

-- 2. 查看數據
select count(*) from log_par;
select count(*) from only log_par;

15:53:30 [local]:5432 dev@devdb=> \dt+ log_par*
                              List of relations
 Schema |      Name      |       Type        | Owner |  Size   | Description 
--------+----------------+-------------------+-------+---------+-------------
 dev    | log_par        | partitioned table | dev   | 0 bytes | 
 dev    | log_par_202001 | table             | dev   | 1960 kB | 
 dev    | log_par_202002 | table             | dev   | 1712 kB | 
 dev    | log_par_his    | table             | dev   | 1960 kB | 
(4 rows)

15:53:30 [local]:5432 dev@devdb=>
內置分區表與其分區的繼承關系
select 
    nmsp_parent.nspname as parent_schema, 
    parent.relname as parent,
    nmsp_child.nspname as child_schema,
    child.relname as child_schema
from pg_inherits join pg_class parent
    on pg_inherits.inhparent = parent.oid join pg_class child
    on pg_inherits.inhrelid = child.oid join pg_namespace nmsp_parent
    on nmsp_parent.oid = parent.relnamespace join pg_namespace nmsp_child
    on nmsp_child.oid = child.relnamespace
where 
    parent.relname = 'log_par';
    
 parent_schema | parent  | child_schema |  child_schema  
---------------+---------+--------------+----------------
 dev           | log_par | dev          | log_par_202001
 dev           | log_par | dev          | log_par_202002
 dev           | log_par | dev          | log_par_his
(3 rows)

添加分區

添加分區
CREATE TABLE log_par_202101 PARTITION OF log_par FOR VALUES FROM ('2021-01-01') TO ('2021-02-01');
創建索引
CREATE INDEX idx_log_par_202101_ctime ON log_par_202101 USING btree(create_time);

刪除分區

1. 直接刪分區方式
DROP TABLE log_par_202101;
2. 解綁分區方式
-- 解綁分區,分區和數據仍然保留
ALTER TABLE log_par DETACH PARTITION log_par_202101;
  
-- 恢復分區
ALTER TABLE log_par ATTACH PARTITION log_par_202101 FOR VALUES FROM ('2021-01-01') TO ('2021-02-01');


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM