ALTER TABLE PARTITION
功能描述
修改表分区,包括增加/删除分区、切割/合并分区、清空分区、移动分区表空间、交换分区、重命名分区、开启/关闭分区自动扩展功能,以及修改分区属性等。
注意事项
- 只有分区表的所有者或者被授予了分区表ALTER权限的用户有权限执行ALTER TABLE PARTITION命令,当三权分立开关关闭时,系统管理员默认拥有此权限。
- 添加分区的表空间不能是pg_global。
- 添加分区的名称不能与该分区表已有分区的名称相同。
- 添加分区的分区键值要和分区表的分区键类型一致。
- 若添加RANGE分区,添加分区键值要大于分区表中最后一个范围分区的上边界。
- 若添加LIST分区,添加分区键值不能与现有分区键值重复。
- 不支持添加哈希分区。
- 如果目标分区表中已有分区数达到了最大值1048575,则不能继续添加分区。
- 当分区表只有一个分区时,不能删除该分区。
- 选择分区使用PARTITION FOR(),括号里指定值个数应该与定义分区时使用的列个数相同,并且一一对应。
- 哈希分区表不支持切割分区,不支持合并分区,不支持添加/删除分区。
- 删除、切割、合并、清空、交换分区的操作会使Global索引失效,可以申明UPDATE GLOBAL INDEX子句同步更新索引。
- 如果删除、切割、合并、清空、交换分区操作不申明UPDATE GLOBAL INDEX子句,并发的DML业务有可能因为索引不可用而报错。
- 若设置参数enable_gpi_auto_update为on,即使不申明UPDATE GLOBAL INDEX子句,也会自动更新Global索引。
语法格式
修改分区表分区包括修改表分区主语法、修改表分区名称的语法。
- 修改表分区主语法。
ALTER TABLE [ IF EXISTS ] { table_name [*] | ONLY table_name | ONLY ( table_name ) } action [, ... ];其中action统指如下分区维护子语法。当存在多个分区维护子句时,保证了分区的连续性,无论这些子句的排序如何,M-Compatibility总会先执行DROP PARTITION再执行ADD PARTITION操作,最后顺序执行其它分区维护操作。move_clause | exchange_clause | merge_clause | split_clause | add_clause | drop_clause | truncate_clause |- move_clause子语法用于移动分区到新的表空间。
MOVE PARTITION { partition_name | FOR ( partition_value [, ...] ) } TABLESPACE tablespacename - exchange_clause子语法用于把普通表的数据迁移到指定的分区。
EXCHANGE PARTITION { partition_name | FOR ( partition_value [, ... ] ) } WITH TABLE {[ ONLY ] ordinary_table_name | ordinary_table_name * | ONLY ( ordinary_table_name )} [ { WITH | WITHOUT } VALIDATION ] [ UPDATE GLOBAL INDEX ] [ [NO] UPDATE DISTRIBUTED GLOBAL INDEX]进行交换的普通表和分区必须满足如下条件:
- 普通表和分区的列数相同,对应列的信息严格一致,包括:列名、列的数据类型、列约束、列的Collation信息、列的存储参数、列的压缩信息等。
- 普通表和分区的表压缩信息严格一致。
- 普通表索引和分区Local索引个数相同,且对应索引的信息严格一致。
- 普通表和分区的表约束个数相同,且对应表约束的信息严格一致。
- 普通表不可以是临时表,分区表只能是范围分区表,列表分区表,哈希分区表。
- 完成交换后,普通表和分区的数据被置换,同时普通表和分区的表空间信息被置换。此时,普通表和分区的统计信息变得不可靠,需要对普通表和分区重新执行analyze。
- 由于非分区键不能建立本地唯一索引,只能建立全局唯一索引,所以如果普通表含有唯一索引时,可能会导致无法交换数据。
如果需要进行数交换数据操作可以通过创建中间表的方式。先将分区数据插入到中间表,truncate分区,普通表数据插入分区表,drop普通表,重命名中间表的方式完成数据交换操作。
- 如果在普通表/分区表上进行了DROP COLUMN操作,被删除的列依然物理存在,则需要保证普通表和分区的被删除列严格对齐才能交换成功。
- EXCHANGE PARTITION partition_name:
- 当partition_name为一级分区名时,进行交换的是一级分区和普通表;当partition_name为二级分区时, 进行交换的是二级分区和普通表。
- 不支持在二级分区表中交换一级分区和普通表。
- 进行分区交换后,自增列不会被重置。
- merge_clause子语法用于把多个分区合并成一个分区。一个命令中合并的源分区上限为300。
MERGE PARTITIONS { partition_name } [, ...] INTO PARTITION partition_name [ TABLESPACE tablespacename ] [ UPDATE GLOBAL INDEX ]
- 对于范围分区,MERGE分区要求源分区的范围连续递增,且MERGE后的分区名可以与最后一个源分区名相同;对于列表分区,则源分区无顺序要求,且MERGE后的分区名可以与任一源分区名相同。如果MERGE后的分区名与源分区名相同,视为同一个分区。
- USTORE存储引擎表不支持在事务块/存储过程中执行ALTER TABLE MERGE PARTITIONS的操作。
- split_clause子语法用于把一个分区切割成多个分区。
SPLIT PARTITION { partition_name | FOR ( partition_value [, ...] ) } { split_point_clause | no_split_point_clause } [ UPDATE GLOBAL INDEX ]
- SPLIT后的分区名可以与源分区名相同,将视为不同的分区。
- 未打开guc参数enable_ilm的情况下,如果使用split_clause子语法把一个带有ilm policy的分区分割成多个分区,新分区不继承ilm policy。
- 范围分区表指定切割点split_point_clause的语法为:
AT ( partition_value ) INTO ( PARTITION partition_name [ TABLESPACE tablespacename ] , PARTITION partition_name [ TABLESPACE tablespacename ] )
切割点的大小要位于正在被切割的分区的分区键范围内,指定切割点的方式只能把一个分区切割成两个新分区。
- 范围分区表不指定切割点no_split_point_clause的语法为:
INTO { ( partition_less_than_item [, ...] ) | ( partition_start_end_item [, ...] ) }
- 不指定切割点的方式,partition_less_than_item指定的第一个新分区的分区键要大于正在被切割的分区的前一个分区(如果存在)的分区键,partition_less_than_item指定的最后一个分区的分区键要等于正在被切割的分区的分区键。
- 不指定切割点的方式,partition_start_end_item指定的第一个新分区的起始点(如果存在)必须等于正在被切割的分区的前一个分区(如果存在)的分区键,partition_start_end_item指定的最后一个分区的终止点(如果存在)必须等于正在被切割的分区的分区键。no_split_point_clause
- partition_less_than_item支持的分区键个数最多为16,而partition_start_end_item仅支持1个分区键,其支持的数据类型请参见PARTITION BY RANGE [COLU...。
- 在同一语句中partition_less_than_item和partition_start_end_item两者不可同时使用;不同split语句之间没有限制。
- 分区项partition_less_than_item的语法如下,其中最后一个分区可以不写分区范围定义,即VALUES LESS THAN (partition_value)部分,默认继承源分区范围定义的上界值。
PARTITION partition_name VALUES LESS THAN {( { partition_value | MAXVALUE } [, ...] ) | MAXVALUE } [ TABLESPACE tablespacename ]
RANGE分区时支持MAXVALUE关键字不带括号,不支持在二级分区的子分区中使用,不支持在分区字段为多列的场景使用。
- 分区项partition_start_end_item的语法如下,其约束请参见START END语法描述。
PARTITION partition_name { {START(partition_value) END ({partition_value | MAXVALUE})} | {START(partition_value)} | {END({partition_value | MAXVALUE})} } [TABLESPACE tablespace_name] - 列表分区表指定切割点split_point_clause的语法如下。
VALUES ( partition_value_list ) INTO ( PARTITION partition_name[ TABLESPACE tablespacename ] , PARTITION partition_name [ TABLESPACE tablespacename ] )
切割点必须是源分区的一个非空真子集,指定切割点的方式只能把一个分区切割成两个新分区。
- 列表分区表不指定切割点no_split_point_clause的语法如下,其中最后一个分区不能写分区范围定义,即VALUES (partition_value_list)部分,其范围等于源分区去掉其他子分区后的剩余集合。
INTO ( PARTITION partition_name VALUES (partition_value_list) [ TABLESPACE tablespacename ][, ...] )
- 最后一个新分区不能写分区范围定义,其范围等于源分区去掉其他子分区后的剩余集合。
- 不指定切割点的方式,每一个新分区都必须是源分区的一个非空真子集,且互不交叉。
- add_clause子语法用于为指定的分区表添加一个或多个分区。
ADD PARTITION({partition_less_than_item | partition_start_end_item | partition_list_item}[, ...])分区项partition_list_item的语法为:PARTITION partition_name VALUES [IN] (list_values_clause) [ TABLESPACE tablespacename ]
- partition_list_item支持最多16个分区键,其支持的数据类型请参见PARTITIONBYLIST[COLUMNS](partition_key)。
- 间隔/哈希分区表不支持添加分区。
- IN不支持在二级分区的子分区中使用。
- drop_clause子语法用于删除分区表中的指定分区。
DROP PARTITION partition_name
- 哈希分区表不支持删除分区。
- 当分区表只有一个分区时,不能删除该分区。
- truncate_clause子语法用于清空分区表中的指定分区。
TRUNCATE PARTITION { { ALL | partition_name [, ...] } | FOR ( partition_value [, ...] ) } [ UPDATE GLOBAL INDEX ]
- move_clause子语法用于移动分区到新的表空间。
- 修改表分区名称的语法。
ALTER TABLE [ IF EXISTS ] table_name RENAME PARTITION { partition_name | FOR ( partition_value [, ...] ) } TO partition_new_name;
参数说明
- table_name
分区表名。
取值范围:已存在的分区表名。
- partition_name
分区名。
取值范围:已存在的分区名。
- tablespacename
指定分区要移动到哪一个表空间。
取值范围:已存在的表空间名。
注:需要在非M-Compatibility数据库中创建或删除tablespace。
- partition_value
分区键值。
通过PARTITION FOR ( partition_value [, ...] )子句指定的这一组值,可以唯一确定一个分区。
取值范围:需要进行操作的分区的分区键的取值范围。
- ordinary_table_name
进行迁移的普通表的名称。
取值范围:已存在的普通表名。
- { WITH | WITHOUT } VALIDATION
在进行数据迁移时,是否检查普通表中的数据满足指定分区的分区键范围。
取值范围:
- WITH:对于普通表中的数据要检查是否满足分区的分区键范围,如果有数据不满足,则报错。
- WITHOUT:对于普通表中的数据不检查是否满足分区的分区键范围。
默认是WITH状态。
由于检查比较耗时,特别是当数据量很大的情况下。所以在保证当前普通表中的数据满足分区的分区键范围时,可以加上WITHOUT来指明不进行检查。
- partition_new_name
分区的新名称。
取值范围:字符串,要符合标识符说明。
- { ALL | partition_name [, ...] }
ALL:清除所有分区数据。
partition_name:目标分区表的分区名。支持一级分区名和二级分区名。
取值范围:已存在的分区名。
- UPDATE GLOBAL INDEX
如果使用该参数,则会更新分区表上的所有全局索引,以确保使用全局索引可以查询出正确的数据。该参数要求表为段页表,同时要求表为hash bucket表。 如果不使用该参数,则分区表上的所有全局索引将会失效。
- UPDATE DISTRIBUTED GLOBAL INDEX
如果使用该参数,则会更新分区表上的所有全局索引,以确保使用全局索引可以查询出正确的数据。如果不使用该参数,则分区表上的所有全局索引将会失效。在该参数前添加NO参数等效于不使用该参数。
示例
- 修改表分区名称
--创建前置分区表。 m_db=# CREATE TABLE test_p1 (col1 INT, col2 INT) PARTITION BY RANGE (col1) ( PARTITION p1 VALUES LESS THAN (10), PARTITION p2 VALUES LESS THAN (20), PARTITION p3 VALUES LESS THAN (MAXVALUE) ); --修改分区名称。 m_db=# ALTER TABLE test_p1 RENAME PARTITION p3 TO pmax; --查询分区信息。 m_db=# SELECT relname, boundaries, oid FROM pg_partition WHERE parentid='test_p1'::regclass AND parttype <> 'r'; relname | boundaries | oid ---------+------------+------- p1 | {10} | 17066 p2 | {20} | 17067 pmax | {NULL} | 17068 (3 rows)
- 分区交换
--创建普通表,插入数据。 m_db=# CREATE TABLE test_ep1(col1 INT,col2 INT); m_db=# INSERT INTO test_ep1 VALUES (GENERATE_SERIES(1,9), 1000); --迁移普通表数据到指定分区。 m_db=# ALTER TABLE test_p1 EXCHANGE PARTITION p1 WITH TABLE test_ep1; --查询。 m_db=# SELECT COUNT(*) FROM test_p1 PARTITION (p1); count ------- 9 (1 row) --删除表test_ep1 m_db=# DROP TABLE test_ep1;
- 分区合并
--将test_p1表中p2,pmax分区合并到pmax中。 m_db=# ALTER TABLE test_p1 MERGE PARTITIONS p2,pmax INTO PARTITION pmax; --查看分区。 m_db=# SELECT relname, boundaries, oid FROM pg_partition WHERE parentid='test_p1'::regclass AND parttype <> 'r' order by 1; relname | boundaries | oid ---------+------------+------- p1 | {10} | 17066 pmax | {NULL} | 17070 (2 rows) --删除表和表空间。 m_db=# DROP TABLE test_p1;
- 切割分区
--建表。 m_db=# CREATE TABLE test_r1 (col1 INT,col2 INT) PARTITION BY RANGE (col1)( PARTITION p1 VALUES LESS THAN (10), PARTITION pmax VALUES LESS THAN (MAXVALUE) ); --切割分区。 m_db=# ALTER TABLE test_r1 SPLIT PARTITION pmax AT (20) INTO (PARTITION p2, PARTITION pmax); m_db=# ALTER TABLE test_r1 SPLIT PARTITION pmax INTO ( PARTITION p3 VALUES LESS THAN (30), PARTITION pmax VALUES LESS THAN (MAXVALUE) ); --查询。 m_db=# SELECT relname, boundaries, oid FROM pg_partition WHERE parentid='test_r1'::regclass AND parttype <> 'r' order by 1; relname | boundaries | oid ---------+------------+------- p1 | {10} | 17088 p2 | {20} | 17090 p3 | {30} | 17092 pmax | {NULL} | 17093 (4 rows) --删除表test_r1。 m_db=# DROP TABLE test_r1;--建表。 m_db=# CREATE TABLE test_r2(col1 INT, col2 INT) PARTITION BY RANGE (col1)( PARTITION p1 START(1) END(10), PARTITION p2 START(10) END(20), PARTITION pmax START(20) END(MAXVALUE) ); --切割分区。 m_db=# ALTER TABLE test_r2 SPLIT PARTITION pmax INTO ( PARTITION p3 START(20) END(30), PARTITION pmax START(30) END (MAXVALUE) ); --查看。 m_db=# SELECT relname, boundaries, oid FROM pg_partition WHERE parentid='test_r2'::regclass AND parttype <> 'r' order by 1; relname | boundaries | oid ---------+------------+------- p1_0 | {1} | 17112 p1_1 | {10} | 17113 p2 | {20} | 17114 p3 | {30} | 17116 pmax | {NULL} | 17117 (5 rows) --删除表test_r2。 m_db=# DROP TABLE test_r2;--建表。 m_db=# CREATE TABLE test_l1(col1 INT, col2 INT) PARTITION BY LIST(col1)( PARTITION p1 VALUES (10,20), PARTITION p2 VALUES (30,40) ); --切割分区。 m_db=# ALTER TABLE test_l1 SPLIT PARTITION p1 VALUES (10) INTO (PARTITION p1_1, PARTITION p1_2); m_db=# ALTER TABLE test_l1 SPLIT PARTITION p2 INTO (PARTITION p3_1 VALUES(30), PARTITION p3_2); --查看。 m_db=# SELECT relname, boundaries, oid FROM pg_partition WHERE parentid='test_l1'::regclass AND parttype <> 'r' order by 1; relname | boundaries | oid ---------+------------+------- p1_1 | {10} | 17132 p1_2 | {20} | 17133 p3_1 | {30} | 17134 p3_2 | {40} | 17135 (4 rows) --删除表test_l1。 m_db=# DROP TABLE test_l1;
- 添加分区
--建表。 m_db=# CREATE TABLE test_p2 (col1 INT, col2 INT) PARTITION BY RANGE (col1)( PARTITION p1 VALUES LESS THAN (10), PARTITION p2 VALUES LESS THAN (20) ); --添加分区。 m_db=# ALTER TABLE test_p2 ADD PARTITION (PARTITION p3 VALUES LESS THAN (30)); --删除表test_p2。 m_db=# DROP TABLE test_p2;--建表。 m_db=# CREATE TABLE test_p3 (col1 INT, col2 INT) PARTITION BY LIST(col1)( PARTITION p1 VALUES (1), PARTITION p2 VALUES (2) ); --添加分区。 m_db=# ALTER TABLE test_p3 ADD PARTITION (PARTITION p3 VALUES (3)); --删除表test_p3。 m_db=# DROP TABLE test_p3; --进入M模式。 --建表 m_db=# CREATE TABLE addpart1 ( month_code int, num int, name varchar(30) )PARTITION BY RANGE COLUMNS(month_code) ( PARTITION p_202301 VALUES LESS THAN(202302), PARTITION p_202302 VALUES LESS THAN (202303) ); --添加分区 m_db=# ALTER TABLE addpart1 ADD PARTITION (PARTITION p_202303 VALUES LESS THAN(202304)); --删除表 m_db=# DROP TABLE addpart1;
- 删除分区
--建表。 m_db=# CREATE TABLE test_p4 (col1 INT, col2 INT) PARTITION BY LIST(col1)(PARTITION p1 VALUES (1),PARTITION p2 VALUES (2)); --删除test_p3表的p2分区。 m_db=# ALTER TABLE test_p4 DROP PARTITION p2; --查看。 m_db=# SELECT relname, boundaries, oid FROM pg_partition WHERE parentid='test_p4'::regclass; relname | boundaries | oid ---------+------------+------- test_p4 | | 17187 p1 | {1} | 17188 (2 rows) --删除表test_p4。 m_db=# DROP TABLE test_p4;
- 清空分区
--建表。 m_db=# CREATE TABLE test_p5 (col1 INT, col2 INT) PARTITION BY RANGE (col1)( PARTITION p1 VALUES LESS THAN (5), PARTITION p2 VALUES LESS THAN (10) ); --插入数据。 m_db=# INSERT INTO test_p5 VALUES (GENERATE_SERIES(1,9), 100); --查看p2分区数据。 m_db=# SELECT * FROM test_p5 PARTITION (p2); col1 | col2 ------+------ 5 | 100 6 | 100 7 | 100 8 | 100 9 | 100 (5 rows) --清空p2分区的数据。 m_db=# ALTER TABLE test_p5 TRUNCATE PARTITION p2; --清空p1,p2分区数据。 m_db=# ALTER TABLE test_p5 TRUNCATE PARTITION p1,p2; --清空所有分区数据。 m_db=# ALTER TABLE test_p5 TRUNCATE PARTITION ALL; --查看p2分区数据。 m_db=# SELECT * FROM test_p5 PARTITION (p2); col1 | col2 ------+------ (0 rows) --删除表test_p5。 m_db=# DROP TABLE test_p5;