作者:lmj
概述
分区表是把逻辑上的一张表根据某种方案分成几张物理块进行存储。这张逻辑上的表称之为分区表,物理块称之为分区。分区表是一张逻辑表,不存储数据,数据实际是存储在分区上的。分区表和普通表相比具有以下优点:
- 改善查询性能: 对分区对象的查询可以仅搜索自己关心的分区,提高检索效率。
- 增强可用性:如果分区表的某个分区出现故障,表在其他分区的数据仍然可用。
- 方便维护: 如果分区表的某个分区出现故障,需要修复数据,只修复该分区即可
对比
PostgreSQL | OpenGauss/MogDB | |
---|---|---|
实现方式 | 声明式划分(PARTITION) 和继承(INHERIT) | 声明式划分(PARTITION) |
查看表结构 | \d+ 分区表名 | 在pg_partition中查看分区表 |
查看数据 | 直接从分区中查看 | 格式为:分区表名 PARTITION 分区名 |
查看表大小 | \dP+ 分区表名 | pg_total_size() |
索引 | 使用CREATE INDEX ON ONLY在单个分区上创建索引 | 全局索引(GLOBAL)和本地索引(LOCAL) |
子分区 | 支持 | 不支持 |
间隔分区 | 不支持 | 支持(INTERVAL) |
注:由于OpenGauss/MogDB不支持使用继承方式实现分区表,以下讨论均是基于声明式划分来进行对比。为了方便区别,PostgreSQL 使用黑底白字,OpenGauss/MogDB使用白底黑字
示例
实现方式
声明式划分-范围分区
范围分区是根据表的一列或者多列,将要插入表的记录分为若干个范围,这些范围在不同的分区里没有重叠。为每个范围创建一个分区,用来存储相应的数据。
范围分区的分区策略是指记录插入分区的方式。目前范围分区仅支持范围分区策略。
范围分区策略:根据分区键值将记录映射到已创建的某个分区上,如果可以映射到已创建的某一分区上,则把记录插入到对应的分区上,否则给出报错和提示信息。这是最常用的分区策略。
PG - PARTITION BY RANGE(列名)
- 通过指定PARTITION BY子句把 part_range 表创建为分区表,该子句包括分区方法(这个例子中是RANGE)以及用作分区键的列列表。
CREATE TABLE part_range (
id int not null,
col1 varchar(8),
create_date date
) PARTITION BY RANGE (create_date);
- 创建分区。每个分区的定义必须指定对应于父表的分区方法和分区键的边界。注:如果指定的边界使得新分区的值与已有分区中的值重叠,将会导致错误
— 没有必要创建表约束来描述分区的分区边界条件。相反,只要需要引用
分区约束时,分区约束会自动地隐式地从分区边界说明中生成。
CREATE TABLE part_range_1 PARTITION OF part_range
FOR VALUES FROM ('2021-01-01') TO ('2021-04-01');
CREATE TABLE part_range_2 PARTITION OF part_range
FOR VALUES FROM ('2021-04-01') TO ('2021-07-01');
CREATE TABLE part_range_3 PARTITION OF part_range
FOR VALUES FROM ('2021-07-01') TO ('2021-10-01');
CREATE TABLE part_range_4 PARTITION OF part_range
FOR VALUES FROM ('2021-10-01') TO ('2021-12-31');
– 插入测试数据
pg13=> insert into part_range values (1,'aaa','2021-02-10'),(2,'bbb','2021-03-01'),(3,'ccc','2021-02-17'),(4,'ddd','2021-12-04');
INSERT 0 4
image-20211206155459729
OpenGauss/MogDB 从句支持两种语法
可以通过 CREATE TABLE PARTITION查看具体语法
- VALUES LESS THAN语法格式(范围分区策略的分区键最多支持4列):
PARTITION partition_name VALUES LESS THAN ( { partition_value | MAXVALUE } )
每个分区都需要指定一个上边界,分区上边界的类型应当和分区键的类型一致,分区列表是按照分区上边界升序排列的,值较小的分区位于值较大的分区之前。
CREATE TABLE part_range_lessthan (
id int not null,
col1 varchar(8),
create_date date
) PARTITION BY RANGE (create_date)
(
partition part_range_1 values less than('2021-04-01'),
partition part_range_2 values less than('2021-07-01'),
partition part_range_3 values less than('2021-10-01'),
partition part_range_4 values less than(maxvalue)
);
-- 插入测试数据
mogdb=> insert into part_range_lessthan values (1,'aaa','2021-02-10'),(2,'bbb','2021-03-01'),(3,'ccc','2021-02-17'),(4,'ddd','2021-12-04');
INSERT 0 4
mogdb=> \d part_range_lessthan
Table "utest.part_range_lessthan"
Column | Type | Modifiers
-------------+--------------------------------+-----------
id | integer | not null
col1 | character varying(8) |
create_date | timestamp(0) without time zone |
Range partition by(create_date)
Number of partition: 4 (View pg_partition to check each partition range.)
mogdb=> select relname,parttype,boundaries from pg_partition where parentid ='part_range_lessthan'::regclass ;
relname | parttype | boundaries
---------------------+----------+--------------
part_range_lessthan | r |
part_range_1 | p | {2021-04-01}
part_range_2 | p | {2021-07-01}
part_range_3 | p | {2021-10-01}
part_range_4 | p | {NULL}
(5 rows)
- START END语法格式(范围分区策略的分区键仅支持1列)
PARTITION partition_name {START (partition_value) END (partition_value) EVERY (interval_value)} | {START (partition_value) END
在创建、修改分区表时请注意分区表的分区总数不可超过最大限制(32767),单一start 分区后不能借一个单一的end分区,否则会报错。
CREATE TABLE part_range_startend (
id int not null,
col1 varchar(8),
create_date date
) PARTITION BY RANGE (create_date)
(
partition part_range_1 start ('2021-01-01') end ('2021-04-01'),
partition part_range_2 end ('2021-07-01'),
partition part_range_3 start ('2021-07-01'),
partition part_range_4 start ('2021-10-01') end ('2021-12-31')
);
mogdb=> \d part_range_startend
Table "utest.part_range_startend"
Column | Type | Modifiers
-------------+--------------------------------+-----------
id | integer | not null
col1 | character varying(8) |
create_date | timestamp(0) without time zone |
Range partition by(create_date)
Number of partition: 5 (View pg_partition to check each partition range.)
mogdb=> select relname,parttype,boundaries from pg_partition where parentid ='part_range_startend'::regclass ;
relname | parttype | boundaries
---------------------+----------+-------------------------
part_range_startend | r |
part_range_1_0 | p | {"2021-01-01 00:00:00"}
part_range_1_1 | p | {"2021-04-01 00:00:00"}
part_range_2 | p | {"2021-07-01 00:00:00"}
part_range_3 | p | {"2021-10-01 00:00:00"}
part_range_4 | p | {"2021-12-31 00:00:00"}
(6 rows)
注意上面两种从句语法不能混用,START END语法格式使用gs_dump时会转变为VALUES LESS THAN语法格式。
声明式划分-列值分区
列表分区是根据表的一列,将要插入表的记录通过每一个分区中出现的键值划分到对应的分区中,这些键值在不同的分区里没有重叠。为每组键值创建一个分区,用来存储相应的数据。
列表分区策略:根据分区键值将记录映射到已创建的某个分区上,如果可以映射到已创建的某一分区上,则把记录插入到对应的分区上,否则给出报错和提示信息。
PG
CREATE TABLE part_list (
id serial not null,
country varchar(16),
city varchar(16)
) PARTITION BY LIST (city);
CREATE TABLE part_list_1 PARTITION OF part_list
FOR VALUES IN ('henan','jiangxi','guangdong');
CREATE TABLE part_list_2 PARTITION OF part_list
FOR VALUES IN('SanFrancisco','NewYork','Chicago');
OpenGauss/MogDB
CREATE TABLE part_list (
id serial not null,
country varchar(16),
city varchar(16)
) PARTITION BY LIST(city)
(
PARTITION part_list_1 VALUES('henan','jiangxi','guangdong'),
PARTITION part_list_2 VALUES('SanFrancisco','NewYork','Chicago')
);
声明式划分-哈希分区
哈希分区是根据表的一列,为每个分区指定模数和余数,将要插入表的记录划分到对应的分区中,每个分区所持有的行都需要满足条件:分区键的值除以为其指定的模数将产生为其指定的余数。
哈希分区策略:根据分区键值将记录映射到已创建的某个分区上,如果可以映射到已创建的某一分区上,则把记录插入到对应的分区上,否则返回报错和提示信息。
PG
CREATE TABLE part_hash (
id int not null,
name varchar(16)
) PARTITION BY HASH (id);
CREATE TABLE part_hash_1 PARTITION OF part_hash
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE part_hash_2 PARTITION OF part_hash
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE part_hash_3 PARTITION OF part_hash
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE part_hash_4 PARTITION OF part_hash
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
OpenGauss/MogDB
create table part_hash(
id int not null,
name varchar(16)
) partition by hash(id)
(
partition part_hash_1,
partition part_hash_2,
partition part_hash_3,
partition part_hash_4
);
查看表结构
PostgreSQL
\d 分区表
- 查看分区表
- 查看分区
pg_partitioned_table:存放有关表如何被分区的信息
-- r:范围分区 l:列表分区 h:哈希分区
pg13=> select partrelid::regclass as partname,partstrat,partattrs,partclass from pg_partitioned_table ;
partname | partstrat | partattrs | partclass
------------+-----------+-----------+-----------
part_range | r | 3 | 3122
part_list | l | 3 | 3126
part_hash | h | 1 | 10020
(3 rows)
OpenGauss/MogDB - pg_partition
-- r:范围分区,p:分区
mogdb=> select relname,parttype,parentid::regclass,partstrategy,partkey,boundaries from pg_partition where parentid = 'part_range_lessthan'::regclass;
relname | parttype | parentid | partstrategy | partkey | boundaries
---------------------+----------+---------------------+--------------+---------+--------------
part_range_lessthan | r | part_range_lessthan | r | 3 |
part_range_1 | p | part_range_lessthan | r | | {2021-04-01}
part_range_2 | p | part_range_lessthan | r | | {2021-07-01}
part_range_3 | p | part_range_lessthan | r | | {2021-10-01}
part_range_4 | p | part_range_lessthan | r | | {NULL}
(5 rows)
\d+ 分区表名只能查看分区表的字段和索引
结论:PG的 pg_partitioned_table 是查看分区表是属于范围分区还是其他,OpenGauss/Mogdb的 pg_partition 不仅能查看属于什么类型的分区,还能查看该分区表的分区,在PG中查看相关分区表可以直接使用\d 表名查看
查看表数据
PostgreSQL
-- 查看分区表数据。从分区表中查询数据将显示分区表及分区的所有数据
pg13=> select * from part_range;
id | col1 | create_date
----+------+-------------
1 | aaa | 2021-02-10
2 | bbb | 2021-03-01
3 | ccc | 2021-02-17
4 | ddd | 2021-12-04
(4 rows)
-- 通过ONLY关键字实现只对分区表的查询。由于分区表中的数据都被路由到分区中,不存在只在分区表中出现的数据
pg13=> select * from ONLY part_range;
id | col1 | create_date
----+------+-------------
(0 rows)
-- 只查看分区的数据
pg13=> select * from part_range_1 ;
id | col1 | create_date
----+------+-------------
1 | aaa | 2021-02-10
2 | bbb | 2021-03-01
3 | ccc | 2021-02-17
(3 rows)
OpenGauss/MogDB
-- 查看分区表数据。从分区表中查询数据将显示分区表及分区的所有数据
mogdb=> select * from part_range_lessthan ;
id | col1 | create_date
----+------+---------------------
1 | aaa | 2021-02-10 00:00:00
2 | bbb | 2021-03-01 00:00:00
3 | ccc | 2021-02-17 00:00:00
4 | ddd | 2021-12-04 00:00:00
(4 rows)
-- 只查看分区的数据
mogdb=> select * from part_range_lessthan PARTITION (part_range_1);
id | col1 | create_date
----+------+---------------------
1 | aaa | 2021-02-10 00:00:00
2 | bbb | 2021-03-01 00:00:00
3 | ccc | 2021-02-17 00:00:00
(3 rows)
查看表大小
PG
-- 法一:
pg13=> select pg_size_pretty(sum(pg_total_relation_size('p1')+pg_total_relation_size('p2')+pg_total_relation_size('p3')+pg_total_relation_size('p4')+pg_total_relation_size('p5')+pg_total_relation_size('p6_default')));
pg_size_pretty
----------------
80 kB
(1 row)
-- 法二:
pg13=> \dP+ part_test
List of partitioned relations
Schema | Name | Owner | Type | Parent name | Table | Total size | Description
--------+-----------+-------+-------------------+-------------+-------+------------+-------------
public | part_test | utest | partitioned table | | | 80 kB |
(1 row)
OpenGauss/MogDB
mogdb=> select pg_size_pretty(pg_total_relation_size('part_test')) ;
pg_size_pretty
----------------
80 kB
(1 row)
索引
PoatgreSQL - 可以在单个分区上创建索引
-- 在父表创建索引即可,PostgreSQL会自动在所有子分区创建对应的索引
pg13=> create index col1_parent_idx on part_range(col1) ;
CREATE INDEX
pg13=> create index createdate_part_list_1_idx on part_range(create_date) ;
CREATE INDEX
pg13=> \d+ part_range
Partitioned table "public.part_range"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
-------------+----------------------+-----------+----------+---------+----------+--------------+-------------
id | integer | | not null | | plain | |
col1 | character varying(8) | | | | extended | |
create_date | date | | | | plain | |
Partition key: RANGE (create_date)
Indexes:
"col1_parent_idx" btree (col1)
"createdate_part_list_1_idx" btree (create_date)
Partitions: part_range_1 FOR VALUES FROM ('2021-01-01') TO ('2021-04-01'),
part_range_2 FOR VALUES FROM ('2021-04-01') TO ('2021-07-01'),
part_range_3 FOR VALUES FROM ('2021-07-01') TO ('2021-10-01'),
part_range_4 FOR VALUES FROM ('2021-10-01') TO ('2021-12-31')
pg13=> \d+ part_range_1
Table "public.part_range_1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
-------------+----------------------+-----------+----------+---------+----------+--------------+-------------
id | integer | | not null | | plain | |
col1 | character varying(8) | | | | extended | |
create_date | date | | | | plain | |
Partition of: part_range FOR VALUES FROM ('2021-01-01') TO ('2021-04-01')
Partition constraint: ((create_date IS NOT NULL) AND (create_date >= '2021-01-01'::date) AND (create_date < '2021-04-01'::date))
Indexes:
"part_range_1_col1_idx" btree (col1)
"part_range_1_create_date_idx" btree (create_date)
Access method: heap
OpenGauss/MogDB
- OpenGauss/MogDB 分区表支持两种索引:全局(global)索引和本地(local)索引,一个LOCAL索引对应一个具体分区,而GLOBAL索引则对应整个分区表默认索引是全局索引。
- 分区表创建索引不支持concurrently语法。
- 创建主键约束和唯一约束必须要包含分区字段,创建本地唯一索引必须要包含分区字段,但是创建全局唯一索引没有这个限制。
-- 创建global 索引
mogdb=> create index col1_parent_idx on part_range_lessthan(col1);
CREATE INDEX
-- 创建 LOCAL 索引
--- 不指定索引分区名称
mogdb=> create index local_no_id_idx on part_range_lessthan(id) local ;
CREATE INDEX
--- 指定索引分区名称
CREATE INDEX local_yes_createdate_idx ON part_range_lessthan (create_date) LOCAL
(
PARTITION local_createdate_part_range_1_index,
PARTITION local_createdate_part_range_2_index,
PARTITION local_createdate_part_range_3_index,
PARTITION local_createdate_part_range_4_index
);
**注:**当指定索引分区名称时,不能只在部分分区上指定索引名,否则会报错。
**结论:**PostgreSQL 支持在每个分区上单独创建索引,当然也支持在分区表上创建索引,此时创建的索引也会应用到每个分区上;OpenGauss/MogDB 支持 global 索引(相当于PG的在分区表上直接创建索引),而 local 索引不能只在单独的一个分区上创建索引,OpenGauss/MogDB 的 local 索引相当于创建一个索引只是在各个分区上的索引名不同,允许进行修改,也能起到管理每个分区的索引的功能。
PG子分区
分区本身被定义为分区表,这种用法叫做子分区
为了实现子分区,可以在创建分区的命令中指定 PARTITION BY 子句。
CREATE TABLE part_range_1 PARTITION OF part_range
FOR VALUES FROM ('2021-01-01') TO ('2021-04-01')
PARTITION BY RANGE(col1);
OpenGauss/MogDB间隔分区
间隔分区是一种特殊的范围分区,相比范围分区,新增间隔值定义,当插入记录找不到匹配的分区时,可以根据间隔值自动创建分区。
间隔分区只支持基于表的一列分区,并且该列只支持TIMESTAMP[§] [WITHOUT TIME ZONE]、TIMESTAMP[§] [WITH TIME ZONE]、DATE数据类型。
间隔分区策略:根据分区键值将记录映射到已创建的某个分区上,如果可以映射到已创建的某一分区上,则把记录插入到对应的分区上,否则根据分区键值和表定义信息自动创建一个分区,然后将记录插入新分区中,新创建的分区数据范围等于间隔值。
创建间隔分区表sales,初始包含2个分区,分区键为DATE类型。 分区的范围分别为:time_id < ‘2021-12-01 00:00:00’,
‘2021-12-01 00:00:00’ <= time_id < ‘2021-12-02 00:00:00’ 。
--创建表sales
openGauss=# CREATE TABLE sales
(prod_id NUMBER(6),
cust_id NUMBER,
time_id DATE,
channel_id CHAR(1),
promo_id NUMBER(6),
quantity_sold NUMBER(3),
amount_sold NUMBER(10,2)
)PARTITION BY RANGE (time_id)
INTERVAL('1 day')
( PARTITION p1 VALUES LESS THAN ('2021-12-01 00:00:00'),
PARTITION p2 VALUES LESS THAN ('2021-12-02 00:00:00')
);
-- 数据插入分区p1
openGauss=# INSERT INTO sales VALUES(1, 12, '2021-01-10 00:00:00', 'a', 1, 1, 1);
-- 数据插入分区p2
openGauss=# INSERT INTO sales VALUES(1, 12, '2021-12-01 00:00:00', 'a', 1, 1, 1);
-- 查看分区信息
openGauss=# SELECT t1.relname, partstrategy, boundaries FROM pg_partition t1, pg_class t2 WHERE t1.parentid = t2.oid AND t2.relname = 'sales' AND t1.parttype = 'p';
relname | partstrategy | boundaries
---------+--------------+-------------------------
p1 | r | {"2021-12-01 00:00:00"}
p2 | r | {"2021-12-02 00:00:00"}
(2 rows)
-- 插入数据没有匹配的分区,新创建一个分区,并将数据插入该分区
-- 新分区的范围为 '2021-12-05 00:00:00' <= time_id < '2021-12-06 00:00:00'
openGauss=# INSERT INTO sales VALUES(1, 12, '2021-12-05 00:00:00', 'a', 1, 1, 1);
-- 插入数据没有匹配的分区,新创建一个分区,并将数据插入该分区
-- 新分区的范围为 '2021-12-03 00:00:00' <= time_id < '2021-12-04 00:00:00'
openGauss=# INSERT INTO sales VALUES(1, 12, '2021-12-03 00:00:00', 'a', 1, 1, 1);
-- 查看分区信息
openGauss=# SELECT t1.relname, partstrategy, boundaries FROM pg_partition t1, pg_class t2 WHERE t1.parentid = t2.oid AND t2.relname = 'sales' AND t1.parttype = 'p';
relname | partstrategy | boundaries
---------+--------------+-------------------------
sys_p1 | i | {"2021-12-06 00:00:00"}
sys_p2 | i | {"2021-12-04 00:00:00"}
p1 | r | {"2021-12-01 00:00:00"}
p2 | r | {"2021-12-02 00:00:00"}
(4 rows)
其他对比
前提
分区表:part_test
分区:p1,p2,p3,p4,p5,p6
pg_class
- 分区表
-- PostgreSQL 分区表:分区表的 relfilenode = 0
pg13=> select oid,relname,reltype,relfilenode from pg_class where relname = 'part_test';
oid | relname | reltype | relfilenode
-------+-----------+---------+-------------
24641 | part_test | 24643 | 0
(1 row)
-- OpenGauss/MogDB 分区表
mogdb=> select oid,relname,reltype,relfilenode from pg_class where relname = 'part_test';
oid | relname | reltype | relfilenode
-------+-----------+---------+-------------
24577 | part_test | 24579 | 24577
(1 row)
- 分区
- PostgreSQL 分区
pg13=> select oid,relname,reltype,relfilenode from pg_class where relname = 'p1';
oid | relname | reltype | relfilenode
-------+---------+---------+-------------
24644 | p1 | 24646 | 24644
(1 row)
-- OpenGauss/MogDB 分区:在 pg_class 中没有对应分区的 oid
mogdb=> select oid,relname,reltype,relfilenode from pg_class where relname = 'p1';
oid | relname | reltype | relfilenode
-----+---------+---------+-------------
(0 rows)
–# 结论:PG中的分区在 pg_class 中存在对应的 oid,而 OpenGauss/MogDB 中的分区在 pg_class 中却没有显示
文件位置
-- PostgreSQL:分区表有对应的 oid ,但是却找不到对应的文件位置;分区可以查找到对应的文件。
pg13=> select pg_relation_filepath('part_test');
pg_relation_filepath
----------------------
(1 row)
pg13=> select pg_relation_filepath('p1');
pg_relation_filepath
----------------------
base/16582/24644
(1 row)
pg13=> \!
[postgres@pg13 ~]$ ll /soft/data6000/base/16582/24644
-rw-------. 1 postgres postgres 8192 Dec 12 14:37 /soft/data6000/base/16582/24644
-- OpenGauss/MogDB:可以在数据中通过函数查找到分区表对应的文件位置,但是在目录中却找不到
mogdb=> select pg_relation_filepath('part_test');
pg_relation_filepath
----------------------
base/16867/24577
(1 row)
mogdb=> \!
[omm@mogdb ~]$ ll /mogdb/data/db1/base/16867/24577
ls: cannot access /mogdb/data/db1/base/16867/24577: No such file or directory
[omm@mogdb ~]$ exit
exit
mogdb=> select pg_relation_filepath('p1');
ERROR: relation "p1" does not exist
mogdb=> select oid,relname,parentid::regclass,boundaries from pg_partition where parentid = 'part_test'::regclass;
oid | relname | parentid | boundaries
-------+-----------+-----------+------------
24580 | part_test | part_test |
24581 | p1 | part_test | {10}
24634 | p5 | part_test | {50}
24638 | p6 | part_test | {NULL}
24653 | p4 | part_test | {40}
(5 rows)
mogdb=> select pg_partition_filepath(24581);
pg_partition_filepath
-----------------------
base/16867/24581
(1 row)
mogdb=> \!
[omm@mogdb bin]$ ll /mogdb/data/db1/base/16867/24581
-rw------- 1 omm dbgrp 8192 Dec 12 18:23 /mogdb/data/db1/base/16867/24581
[omm@mogdb bin]$ ll /mogdb/data/db1/base/16867/24580
ls: cannot access /mogdb/data/db1/base/16867/24580: No such file or directory
注意事项
- 可以类似的理解为:PostgreSQL 的分区表和分区是实际存在的表,对分区进行的操作等同于对普通表进行操作;而OpenGauss/MogDB 的分区是在分区表中的分块,属于分区表,并不是真正的一张普通的表,同样也就不能进行类似于普通表的操作。
- 范围分区的每个区间:[起始值,终点值)
- OpenGauss/MogDB唯一约束和主键约束的约束键包含所有分区键将为约束创建LOCAL索引,否则创建GLOBAL索引。
- OpenGauss/MogDB目前哈希分区和列表分区仅支持单列构建分区键,暂不支持多列构建分区键。