基于PostgreSQL的声明式分区表实践

本文对PostgreSQL 11中声明式分区表进行实践
基于PostgreSQL的声明式分区表实践
abstract.png

概述

在PostgreSQL 10之前,需要使用继承的方式来实现分区表,即所谓的继承式分区表。从PostgreSQL 10开始,引入了声明式分区表。支持Range、List两种分区方式。而在PostgreSQL 11中,声明式分区表的功能得到进一步增强。提供了对Hash分区的支持

Range范围分区

所谓范围分区,指的是根据分区键的值将记录存储到符合范围的分区表中。这里我们先建立使用范围分区的stu_info父表。其中,分区键为age字段

-- 创建父表,使用范围分区,分区键为age字段
create table stu_info (
    id int not null,
    name varchar(100null,
    age int not null,
    primary key (id,age) -- 父表的主键必须包含分区字段
partition by range (age);

然后建立下述3张分区表。其中,MINVALUE、MAXVALUE 分别表示无限小值、无限大值。需要注意的是对于 from (18) to (60) 这样范围描述而言,该范围包含了临界值18,但不包含临界值60

-- 创建分区表

-- 分区键的值在[-∞,18)范围下,数据存储到该表
create table stu_info_young partition of stu_info for values from (MINVALUEto (18);

-- 分区键的值在[18,60)范围下,数据存储到该表
create table stu_info_middle partition of stu_info for values from (18to (60);

-- 分区键的值在[60,+∞)范围下,数据存储到该表
create table stu_info_old partition of stu_info for values from (60to (MAXVALUE);

现在我们直接向父表插入数据

-- 插入数据
insert into stu_info(idname, age) values (1'Aaron'100);
insert into stu_info(idname, age) values (2'Bob'1);
insert into stu_info(idname, age) values (3'Tom'60);
insert into stu_info(idname, age) values (4'Tony'18);
insert into stu_info(idname, age) values (5'Cat'12);
insert into stu_info(idname, age) values (6'Dog'35);
insert into stu_info(idname, age) values (7'Amy'77);
insert into stu_info(idname, age) values (8'Lucy'-7);
insert into stu_info(idname, age) values (9'David'101);

此时,直接访问父表。可以发现我们能够同时看到分区表中的数据

select * from stu_info;
基于PostgreSQL的声明式分区表实践
figure 1.jpg

通过添加only关键字,可以实现只查看父表中的数据,而不查看分区表中的数据

-- 只查看存储在stu_info父表中的数据,不查看分区表中的数据
select * from only stu_info;

此时会发现查询结果为空。因为记录是存储到各分区表当中,而不是直接存储到父表中的

基于PostgreSQL的声明式分区表实践
figure 2.jpg

当然我们也可以直接访问各分区表,如下所示

select * from stu_info_young;

select * from stu_info_middle;

select * from stu_info_old;
基于PostgreSQL的声明式分区表实践
figure 3.jpg

List列表分区

所谓列表分区,指的是根据分区键的值将记录存储到符合枚举值列表的分区表中。这里我们先建立使用列表分区的goods_info父表。其中,分区键为type字段

-- 创建父表,使用列表分区,分区键为type字段
create table goods_info (
    id int not null,
    name varchar(100null,
    price double precision null,
    type varchar(100null,
    primary key (id,type-- 父表的主键必须包含分区字段
partition by list (type);

然后建立下述3张分区表。此外,在PostgreSQL 11中,提供了default默认分区的支持。用于存储无法匹配任何分区的数据

-- 创建分区表

-- 分区键的值为phone或pc时,数据存储到该表
create table goods_info_3c partition of goods_info for values in ('phone''pc');

-- 分区键的值为fruits或meat时,数据存储到该表
create table goods_info_food partition of goods_info for values in ('fruits''meat');

-- 分区键的值为其他值时,数据存储到该表
create table goods_info_other partition of goods_info default;

现在我们直接向父表插入数据

-- 插入数据
insert into goods_info(idname, price, typeVALUES (1'Orange''3.5''fruits');
insert into goods_info(idname, price, typeVALUES (2'Beef''29.9''meat');
insert into goods_info(idname, price, typeVALUES (3'MacBook Pro''299''pc');
insert into goods_info(idname, price, typeVALUES (4'Nike''77''clothes');
insert into goods_info(idname, price, typeVALUES (5'Chicken''12.55''meat');
insert into goods_info(idname, price, typeVALUES (6'Benz''120000''car');
insert into goods_info(idname, price, typeVALUES (7'XiaoMi 14''199''phone');

此时,直接访问父表。可以发现我们能够同时看到分区表中的数据。同理,如果通过添加only关键字,可以实现只查看父表中的数据,而不查看分区表中的数据。此时会发现查询结果为空。因为记录是存储到各分区表当中,而不是直接存储到父表中的

select * from goods_info;
基于PostgreSQL的声明式分区表实践
figure 4.jpg

当然我们也可以直接访问各分区表,如下所示

select * from goods_info_3c;

select * from goods_info_food;

select * from goods_info_other;
基于PostgreSQL的声明式分区表实践
figure 5.jpg

Hash哈希分区

在PostgreSQL 11中分区方式增加了对Hash分区的支持。其是对分区键的值进行哈希、取模,然后根据余数存储到相应的分区表中。这里我们先建立使用哈希分区的country_code父表。其中,分区键为country_code字段

-- 创建父表,使用哈希分区,分区键为country_code字段
create table country_info (
    id int not null,
    country varchar(100null,
    country_code int null,
    primary key (id,country_code) -- 父表的主键必须包含分区字段
partition by hash (country_code);

然后建立下述3张分区表

-- 创建分区表

-- 对分区键值哈希后,按3取模、余数为0时,数据存储到该表
create table country_info_0 partition of country_info for values with (modulus 3remainder 0);

-- 对分区键值哈希后,按3取模、余数为1时,数据存储到该表
create table country_info_1 partition of country_info for values with (modulus 3remainder 1);

-- 对分区键值哈希后,按3取模、余数为2时,数据存储到该表
create table country_info_2 partition of country_info for values with (modulus 3remainder 2);

现在我们直接向父表插入数据

-- 插入数据
insert into country_info(id, country, country_code) VALUES (3'China'1);
insert into country_info(id, country, country_code) VALUES (6'USA'3);
insert into country_info(id, country, country_code) VALUES (9'UK'0);
insert into country_info(id, country, country_code) VALUES (12'Russia'2);
insert into country_info(id, country, country_code) VALUES (15'Canada'4);
insert into country_info(id, country, country_code) VALUES (18'France'5);
insert into country_info(id, country, country_code) VALUES (21'Japan'6);

此时,直接访问父表。可以发现我们能够同时看到分区表中的数据。同理,如果通过添加only关键字,可以实现只查看父表中的数据,而不查看分区表中的数据。此时会发现查询结果为空。因为记录是存储到各分区表当中,而不是直接存储到父表中的

select * from country_info;
基于PostgreSQL的声明式分区表实践
figure 6.jpg

当然我们也可以直接访问各分区表,如下所示

select * from country_info_0;

select * from country_info_1;

select * from country_info_2;
基于PostgreSQL的声明式分区表实践
figure 7.jpg

常用查询SQL

1. 查询父表的分区类型、分区键

-- 查询父表stu_info的分区类型、分区键
select pg_get_partkeydef('stu_info'::regclass);
基于PostgreSQL的声明式分区表实践
figure 8.jpg

2. 查询分区表的分区约束

-- 查询分区表stu_info_young的分区约束
select pg_get_partition_constraintdef('stu_info_young'::regclass);
基于PostgreSQL的声明式分区表实践
figure 9.jpg

3. 定位记录实际所存储的分区表

相关SQL如下所示,定位父表stu_info中id为2的记录实际存储的分区表

-- 通过系统表pg_class确定该 tableoid 所表示的表
select relname as table_name from pg_catalog.pg_class where relfilenode = (
    -- 获取相应记录的隐藏字段tableoid
    select tableoid from stu_info where id=2
);
基于PostgreSQL的声明式分区表实践
figure 10.jpg

4. 查询分区表的所有父表

因为系统pg_catalog.pg_inherits 记录了父表、分区表间的关联关系。其中,inhrelid表示分区表的oid,inhparent表示父表的oid

基于PostgreSQL的声明式分区表实践
figure 11.jpg

故可通过下述sql进行查询,子查询写法如下

-- 子查询写法

-- 根据 分表tableoid 获取 父表名称
select relname as table_name from pg_catalog.pg_class where relfilenode in (
    -- 根据 分区表tableoid 获取 其所有父表的tableid
    select inhparent from pg_catalog.pg_inherits where inhrelid in (
        -- 获取分区表对应的tableoid
        select relfilenode as tableoid from pg_catalog.pg_class where relname='stu_info_old'
    )
);
基于PostgreSQL的声明式分区表实践
figure 12.jpg

当然也可以使用连接查询,sql如下所示

-- 连接查询写法

select
    c2.relname as table_name
from pg_catalog.pg_class c1
    join pg_catalog.pg_inherits i on i.inhrelid = c1.relfilenode
    join pg_catalog.pg_class c2 on c2.relfilenode = i.inhparent
where
    c1.relname = 'stu_info_old';
基于PostgreSQL的声明式分区表实践
figure 13.jpg

5. 查询父表下的分区表

可以利用如下sql通过子查询获得,sql如下所示

-- 子查询写法

-- 根据分区表tableoid 获取 分区表的表名
select relname as table_name from pg_catalog.pg_class where relfilenode in (
    -- 根据 父表tableoid 获取 分区表的tableid
    select inhrelid as tableid from pg_catalog.pg_inherits where inhparent in (
        -- 获取父表对应的tableoid
        select relfilenode as tableoid from pg_catalog.pg_class where relname='stu_info'
    )
);
基于PostgreSQL的声明式分区表实践
figure 14.jpg

当然也可以使用连接查询,sql如下所示

-- 连接查询写法
select
    c1.relname as table_name
from pg_catalog.pg_class c1
    join pg_catalog.pg_inherits i on i.inhrelid = c1.relfilenode
    join pg_catalog.pg_class c2 on c2.relfilenode = i.inhparent
where
    c2.relname = 'stu_info';
基于PostgreSQL的声明式分区表实践
figure 15.jpg

6. 移除/添加 分区表

可通过如下方式将分区表从父表中移除,此时该分区表将会被转换为普通表。这样父表中将不会再查到该表的数据,但可以通过直接访问该表来获得

-- 从父表stu_info中移除分区表stu_info_young
alter table stu_info detach partition stu_info_young;
基于PostgreSQL的声明式分区表实践
figure 16.jpg

可通过如下方式将向父表中添加某个分区表,此时该普通表将会被转换为分区表。相关数据可通过父表查询获得

-- 向父表stu_info中添加分区表stu_info_young
alter table stu_info attach partition stu_info_young for values from (MINVALUEto (18);
基于PostgreSQL的声明式分区表实践
figure 17.jpg

Note

  • Range、List分区均支持default默认分区。但Hash分区方式不支持default默认分区
  • 在PostgreSQL 11中,在父表创建索引、唯一性约束后,各分区表均会自动创建相应索引、唯一性约束
  • 在父表创建唯一性约束时,必须包含分区键。故,在父表建立主键时也必须包含分区键。因为各分区表中的唯一性约束只能保证数据在分区表中的唯一性。故需要添加分区键来保证其在所有分区表当中的唯一性。例如,对于分区表stu_info_young、stu_info_old而言,其可能各自都有一条id为1的记录,但由于分区键age不一样。故这两条数据在整体上才不会违反主键的唯一性要求
  • 当进行修改操作,如果记录的分区键的值被修改后,导致其需要从原分区表a移动到新分区表b时。需要注意,如果是对父表进行修改操作时,相关记录不仅会修改成功,同时也会完成记录的移动;但如果是对原分区表a进行修改操作的话,则会直接修改失败
  • 使用声明式分区表时,推荐将enable_partition_pruning配置项设置为on,以启用分区过滤功能。这样扫描时直接过滤掉不必要的分区,从而提高查询性能

原文始发于微信公众号(青灯抽丝):基于PostgreSQL的声明式分区表实践

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/270125.html

(0)
李, 若俞的头像李, 若俞

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!