分區表
早在 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操作
創建分區表
步驟
- 創建父表
- 通過 INHERITS 方式創建繼承表,也稱之為子表或分區
- 給子表創建約束
- 給子表創建索引,繼承操作不會繼承父表上的索引
- 在父表上定義 INSERT 、 DELETE 、 UPDATE 觸發器,將 SQL 分發到對應分區(可選)
- 啟用 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');