在 ORACLE 迁移到 MogDB 的过程中,遇到了指定切割点进行分区切割的问题,这里记录如下:

1
2
3
4
5
6
7
8
9
sys@ORA19CK> CREATE TABLE test_split_tbs (
id NUMBER(19) NOT NULL,
ctime DATE NOT NULL
)PARTITION BY RANGE (ctime)
(
PARTITION alarm_20240510 VALUES LESS THAN(TO_DATE(' 2024-05-11 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')) ,
PARTITION ALARM_20500101 VALUES LESS THAN(MAXVALUE)
);
Table created.

查看分区名和 id

1
2
3
4
5
6
7
sys@ORA19CK> select  object_name,subobject_name,object_id  from dba_objects where  OBJECT_NAME='TEST_SPLIT_TBS';

OBJECT_NAME SUBOBJECT_NAME OBJECT_ID
-------------------- -------------------- ----------
TEST_SPLIT_TBS ALARM_20240510 88709
TEST_SPLIT_TBS ALARM_20500101 88710
TEST_SPLIT_TBS 88708

可以看到 ALARM_20500101 的 OBJECT_ID为 88710

切割分区

alter table TEST_SPLIT_TBS split partition alarm_20500101 at (to_date(‘2024-08-01’,’YYYY-MM-DD’)) into (partition alarm_20240731,partition alarm_20500101);

注意:这个切割分区是将原来的分区 alarm_20500101 切割为 alarm_20240731alarm_20500101,也就是新分区还是原来的名字,相当于新增了一个分区。

1
2
3
sys@ORA19CK>alter table TEST_SPLIT_TBS split partition alarm_20500101 at (to_date('2024-08-01','YYYY-MM-DD')) into (partition alarm_20240731,partition alarm_20500101);

Table altered.

可以看到 oracle 可以切割成功。

切割分区之后再查看分区名和对象id

1
2
3
4
5
6
7
8
sys@ORA19CK>select  object_name,subobject_name,object_id  from dba_objects where  OBJECT_NAME='TEST_SPLIT_TBS';

OBJECT_NAME SUBOBJECT_NAME OBJECT_ID
-------------------- -------------------- ----------
TEST_SPLIT_TBS ALARM_20240510 88709
TEST_SPLIT_TBS ALARM_20240731 88711
TEST_SPLIT_TBS ALARM_20500101 88710
TEST_SPLIT_TBS 88708

可以看到切割的源分区 ALARM_20500101 的 OBJECT_ID 仍然为 88710

但是 MogDB 进行切割的时候报错如下:

创建分区表:

1
2
3
4
5
6
7
8
9
10
11
create table test_split_tbs1(
id numeric(19,
0) not null,
ctime timestamp(0) without time zone not null
)
partition by range(ctime)
(partition alarm_20240510
values less than ('2024-05-11 00:00:00'),
partition alarm_20500101
values less than (maxvalue)
);

查看分区名称

1
2
3
4
5
6
7
select
pp.oid,
pp.relname,c.relname ,pp.parentid
from
pg_partition pp join pg_class c on pp.parentid=c.oid
where
pp.parttype = 'p';
1
2
3
4
5
6
7
  oid  |    relname     |    relname     | parentid
-------+----------------+----------------+----------
16397 | alarm_20240510 | test_split_tbs | 16393
16398 | alarm_20500101 | test_split_tbs | 16393
(2 rows)

liups=>
1
2
liups=>alter table TEST_SPLIT_TBS split partition alarm_20500101 at (to_date('2024-08-01','YYYY-MM-DD')) into (partition alarm_20240731,partition alarm_20500101);
ERROR: resulting partition "alarm_20500101" name conflicts with that of an existing partition

MogDB 报 : ERROR: resulting partition "alarm_20500101" name conflicts with that of an existing partition

也就是新分区的名跟原来的分区重名了。

1
AT ( partition_value ) INTO ( PARTITION partition_name [ TABLESPACE tablespacename ] , PARTITION partition_name [ TABLESPACE tablespacename ] )

image-20240804075606479

查看官方手册:https://docs.mogdb.io/zh/mogdb/v5.0/dolphin-alter-table-partition#语法格式

这就是 指定切割点split_point_clause的。

很明显写明了:,指定切割点的方式只能把一个分区切割成两个新分区

但是通过上面很显然 ORACLE是 没有这个限制的。

那去测试下 原生的PG 是否支持呢。

PG 版本是 16.3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
liups=# create table test_split_tbs(
liups(# id numeric(19,
liups(# 0) not null,
liups(# ctime timestamp(0) without time zone not null
liups(# )
liups-# partition by range(ctime)
liups-# (partition alarm_20240510
liups(# values less than ('2024-05-11 00:00:00'),
liups(# partition alarm_20500101
liups(# values less than (maxvalue)
liups(# );
ERROR: syntax error at or near "("
LINE 7: (partition alarm_20240510
^
liups=# select version();
version
-------------------------------------------------------------------------------------------------------------------
PostgreSQL 16.3 on aarch64-unknown-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-22.0.1), 64-bit
(1 row)

liups=#

完蛋,PostgreSQL 竟然都不支持 oracle 创建分区表的语法,在 16.3 不支持,那我相信之前的版本也不支持。那就不测试了。经过查询

那我们知道了 MogDB 在切割分区的时候,新分区不能跟原来的分区重名,怎么解决呢。首先这是在存储过程中实现的,也就是每天从maxvaleu 中分割一个新分区出来,maxvalue 的分区名称不能变,还是原来的。

这能有几个考量:maxvalue 分区的名称是否能发生变化,有人会说了,maxvalue的分区改个名称就好了,加个数字1或者a、b、c之类的就行了,在手动单独执行的时候确实可行,但是如果在存储过程中还有结合业务来看呢,并没有这么简单。这个maxvalue 分区是 alarm_20500101 ,在存储过程中写死的,如果你切割的分区产生了变化,那下一次运行的时候,maxvalue分区名都边了,肯定下一次执行就报错了,都找不到这个 alarm_20500101了。

既然产生了变化,那就存储过程加个判断就好了,定义俩变量,取出 maxvalue 分区的名称,给变量 ,然后另一个变量再maxvalue 上加个数字,那有个问题,每次加一个字符串,多长执行之后那这个分区名就很很长了。。肯定会超过限制的,尤其一天一个分区的,再说了也不美观。还有人说:大月max 小月max1 7月 单独分开,我是没看懂,可能他这个是 按照月分区的,不过给了我一个解决方案就是判断当前日期是偶数还是奇数,如果是偶数maxvalue的分区叫max,奇数就是max1,这也是个解决方案,解决了切割分区的问题,需要加上一个判断当前日期是奇数还是偶数,那有没有一个maxvalue 分区名不变的方案呢,既然他存储过程中写死了分区的名称为 alarm_20500101,那你能确保应用了没有写这个分区名称嘛?

这里我的解决方案是什么呢,其实很简单:

就是每次切割的时候,maxvalues 都切割为 :alarm_20500101_temp,切割完成之后,再重命名。

1
2
alter table TEST_SPLIT_TBS split partition alarm_20500101 at (to_date('2024-08-01','YYYY-MM-DD')) into (partition alarm_20240731,partition alarm_20500101_temp);
ALTER TABLE test_split_tbs RENAME PARTITION alarm_20500101_temp TO alarm_20500101;

也就是在切割完成之后,紧跟着一个重命名,

1
ALTER TABLE test_split_tbs RENAME PARTITION alarm_20500101_temp  TO alarm_20500101;

这样可以顺利切换,并能保证maxvalue 的分区名不变。当然了如果您要说,在重命名需要一定的时间(零点几毫秒吧),业务并发大,影响业务怎么办,如果你有那么大的并发就另说了。

具体的存储过程中的改造的SQL 如下:

1
2
v_Sql:='alter table '||v_TableName||' SPLIT partition ALARM_20500101 at (to_date('''||v_PartDateStr||''',''YYYY-MM-DD'')) INTO  (PARTITION '||v_PartName||', PARTITION ALARM_20500101temp)';
v_Sql_temp:='alter table '||v_TableName||' RENAME partition ALARM_20500101temp to ALARM_20500101';

原文作者: Hi.MogDB

原文链接: https://hi.mogdb.org/posts/65c9694d/

许可协议: 知识共享署名-非商业性使用 4.0 国际许可协议