前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >迁移实战:一次AntDB(基于pgxl分布式架构的数据库)数据库迁移经验分享

迁移实战:一次AntDB(基于pgxl分布式架构的数据库)数据库迁移经验分享

作者头像
数据和云
修改2020-09-14 14:37:47
5.6K0
修改2020-09-14 14:37:47
举报
文章被收录于专栏:数据和云

墨墨导读:AntDB是一款基于PG内核的分布式数据库。根据AntDB官方介绍,该数据具备持续的集群自动高可用,秒级在线扩容,强大的Oracle兼容,异地容灾,sql语句级自定义分片,分布式事务和MVCC,是一款非常强大的企业级国产分布式数据库。由于AntDB在使用过程中,与pgxl很相近,所以本文对pgxl的情况下有一定的帮助。

一、前言

AntDB是一款基于PG内核的分布式数据库。根据AntDB官方介绍,该数据具备持续的集群自动高可用,秒级在线扩容,强大的Oracle兼容,异地容灾,sql语句级自定义分片,分布式事务和MVCC。是一款非常强大的企业级国产分布式数据库。其架构如下(本图参考网络图片):

在当前的情况下,大部分企业关心的是如何将Oracle数据库迁移到mysql数据库、postgresql数据库等开源及国产数据库中。而很少关注这些开源及国产数据库本身的迁移与升级。

由于AntDB在使用过程中,与pgxl很相近,所以本文对pgxl的情况下应该还是有一定的帮助。

二、背景

最近,某客户现场需要将其之前使用的AntDB进行升级。由于本次升级,涉及的版本不同、节点数量不同。因此,综合考虑只能选用逻辑的方式完成本次数据库的迁移和升级。

三、迁移思路

Postgresql中,我们可以通过pg_dump和pg_restore进行逻辑导入和恢复。但是在本次迁移中,源AntDB存放的表数量以及数据量都非常大(表数量基本由22w张,数据量大约有46T)。所以,如果使用pg_dump和pg_restore的方式,其效率将会非常低。

因此,我们则选择使用postgresql中的copy的方式进行迁移。COPY是PostgreSQL中表和标准文件系统文件之间交换数据的方式,可以理解为直接将文件系统文件中的数据直接装载到数据库中,而不是传统的通过insert语句方式逐条插入数据。因此,在postgreSQL中,通过COPY的方式,将会使数据导入更快。

四、迁移步骤

AntDB的迁移总体分为两个步骤: 1)表结构迁移 2)数据迁移

接下来,我将分别分享这两个步骤中的迁移经验和遇到的问题。

1. 表结构迁移

在本场景的迁移过程中,由于源端是基于PostgreSQL 9.6的数据库,目标端是基于PostgreSQL 11.6的数据库。而9.6和11.6两个大版本数据库中很多特性又是不同的,故也为迁移造成一些难度。其中,在本场景中最重要的就是分区表。

PostgreSQL 9.6数据库中,并没有分区表的概念,其分区表则主要是通过继承表+触发器来实现的,表数据是根据触发器条件来写入到不同子表中的;

PostgreSQL 11.6数据库中则加入了分区表的概念,可以直接创建基于基表的分区表,数据可以根据分区键条件插入到各自的分区中。

那么在迁移中遇到的一个难点就是:如何将9.6中的父表、子表的关系转换成11.6中的分区表。

在postgreSQL中,我们无法像Oracle里面的get_ddl函数一样,获取表的创建语句。但是,我们可以通过pg_dump的方式,将PosgreSQL中的表结构导出,生成SQL语句。

因此,我们首先需要从源端数据库中找出分区表和非分区表,通过以下SQL就可以在PostgreSQL中找到分区表和非分区表。

查询分区表有哪些:

代码语言:javascript
复制
select distinct p.relname fq_table
from pg_class p,pg_inherits i
where p.oid=i.inhparent and reltype<>0 order by fq_table;

查询非分区表有哪些:

代码语言:javascript
复制
select tablename from pg_tables where tablename not in
(select partrelid::regclass::text fq_table
from pg_partitioned_table order by fq_table) and tablename not in(select c.relname
from pg_class a left join pg_inherits b  on a.oid=b.inhparent
left  join pg_class c on b.inhrelid=c.oid where c.relname is not null) and schemaname='itv' order by tablename;

确定好分区表和非分区表以后,我们可以将查出的表名写到一个txt文本文件中,然后通过以下脚本来生成pg_dump语句(当然也可以自己写脚本或者程序进行生成,其核心就是生成:pg_dump -h host_name_ip -p port_number -d db_name -U user_name -n schema_name -s -t x -f

代码语言:javascript
复制
vi pg_dump_table.sh

read -p "请输入读取的文件:" read_file
read -p "是否为分区表 ?yes/no:" partition_table
read -p "请输入主机名或者IP:" host_name_ip
read -p "请输入端口:" port_number
read -p "请输入数据库名字:" db_name
read -p "请输入用户名:" user_name
read -p "请输入模式名:" schema_name

if [ "$partition_table" = "yes" ] || [ "$partition_table" = "y" ]; then
        echo '' > $read_file.sh
        for x in `cat $read_file.txt`
        do
                echo pg_dump -h $host_name_ip -p $port_number -d $db_name -U $user_name -n $schema_name -s -t $x -f $x\_MyPartition.sql >> $read_file.sh
        done
                echo script complete.
elif [ "$partition_table" = "no" ] || [ "$partition_table" = "no" ]; then
        echo '' > $read_file.sh
        for x in `cat $read_file.txt`
        do
                echo pg_dump -h $host_name_ip -p $port_number -d $db_name -U $user_name -n $schema_name -s -t $x -f $x.sql >> $read_file.sh
        done
                echo script complete.
else
        echo 'please input yes/no'
fi

生成的.sh脚本中的内容即为pg_dump导出表结构的语句。

但是,按照我们在上面提到的PostgreSQL 9.6中没有分区表概念。所以,我们导出的表结构也不会有分区键在里面。

因此,我们就需要手动去修改这些表结构创建语句,根据其实际的分区键添加partition by (segment_name)。这个工作,需要对所有表的分区情况和分区键比较熟悉,整个过程是一个体力活,所以不展开叙述。但当我们了解所有分区表的分区定义后,也可以直接通过脚本在文本上进行增加,举例如下(核心就是通过sed匹配建表语句中的结尾括号,然后进行替代):

代码语言:javascript
复制
vi modify_partition_sql.sh

mkdir -p ./partition_table_by_date_no
mkdir -p ./partition_table_by_date_no_result
mv ./*_MyPartition.sql ./partition_table_by_date_no/

for x in `ls ./partition_table_by_date_no/`
do
        echo $x
        sed -i "s/^)$/) partition by list(date_no)/g" ./partition_table_by_date_no/$x
        mv ./partition_table_by_date_no/$x ./partition_table_by_date_no_result/$x
done

mv *.sql ./partition_table_by_date_no/
tar -cvf partition_table_by_date_no.tar partition_table_by_date_no/
tar -cvf partition_table_by_date_no_result.tar partition_table_by_date_no_result/

修改完分区表信息后,则可以通过以下脚本直接连接到数据库中执行:

代码语言:javascript
复制
--分区表
vi create_partitionBase_table.sh

for x in `ls partition_table_by_date_no_result`
do
        echo $x >> ./create_partitionBase_table.log
        psql -h antdb01 -p 15432 -Udctest -d dcrptdb1 -f ./partition_table_by_date_no_result/$x >> ./create_partitionBase_table.log
done
--非分区表
vi create_nopartition_table.sh

for x in `ls partition_table_by_date_no`
do
        echo $x >> ./create_nopartition_table.log
        psql -h antdb01 -p 15432 -Udctest -d dcrptdb1 -f ./partition_table_by_date_no/$x >> ./create_nopartition_table.log
done

到这里所有分区基表和非分区表的表结构就创建好了,那么下一步针对分区表我们就要创建对应的分区了。

创建分区首先我们需要从源库将原来的分区信息查出来,故通过以下语句可以查出表的分区信息:

代码语言:javascript
复制
select a.relname,c.relname
from pg_class a left join pg_inherits b  on a.oid=b.inhparent
left  join pg_class c on b.inhrelid=c.oid
 where a.relname = table_name::text;

找到这些分区后,则需要手动根据分区名字创建分区表,以下举例说明:

代码语言:javascript
复制
create table partition_table_name partition of partition_base_table_name for values in('20200201');partition_base_table

另外,在导数过程中,源端数据可能有变化,对分区进行删除,那么部分数据可能已经查到,但不属于任何一个分区,这样在导入数据时则会报错,因此为了保证导数不报错,我们则可以考虑创建一个default分区,用来存放不属于任何分区的数据,以下举例说明:

代码语言:javascript
复制
create table partition_table_default partition of partition_base_table_name default;

至此,所有的表结构就都创建好了。

2. 表数据迁移

表数据迁移过程相对来说比较简单,主要时通过copy from/copy to方式,从源端将数据导出,然后在目标端再进行导入即可。

起初,指定的方案是从目标端登录,以目标端的psql为客户端,远程登录源端的postgreSQL数据库,然后通过以下脚本语句,将数据导为csv格式(脚本模板,&开头都为实际情况下的IP、端口、表名等值):

代码语言:javascript
复制
vi partition_table_name_copy_in_csv.sh

psql -h &host -p &port_number -d &database_name -U &database_name << EOF >> /data_dir/partition_table_name_copy_in_csv.log
set search_path=&schema_name;
\timing on
\copy partition_table_name to '/dir/partition_table_name.csv' with (format csv);
\q
EOF

然后再通过以下脚本,将数据导入:

代码语言:javascript
复制
vi partition_table_name_copy_out_csv.sh

psql -h &host -p &port_number -d &database_name -U &database_name << EOF >> /data_dir/partition_table_name_copy_out_csv.log
set search_path=&schema_name;
\timing on
\copy partition_table_name from '/dir/partition_table_name.csv' with (format csv);
\q
EOF

但是该方案中有个缺点,就是将数据落地为csv格式,会占用实际的空间,1T表可能会生成1T左右的CSV,而在导入过程中,该csv数据是不能删除的。那么实际就会占用2倍的空间;而在实际情况下,单台机器也没有这么打的空间存放csv。所以只能部分表导出后,再执行导入脚本,导入成功后,删除csv文件,再次导出/导入。

这样操作,就会增加迁移的复杂程度和时间。

那么,有没有一种方式可以不把数据进行落地就导入导出呢?

通过对COPY语法的研究,发现在postgreSQL中,存在copy…to stdout和copy…to stdin两种方式,这两种方式表示将数据copy后输出到标准输出(在psql中执行,则会直接打印在屏幕上),而copy…to stdin则表示从标准输入中导入数据(在psql中,会将打印在屏幕上的输出导入导库中)。

所以,通过这两个特性,我们就可以结合Linux的管道符,将两种方式进行连接,然后就可以实现数据不落地的导入。

那么我们该如何判断copy成功了呢?其实,只需要在第二个语句后,把导入的结果指向到一个日志文件即可。示例如下:

代码语言:javascript
复制
psql -h &source_host -p &port_number -U &user -d &user -c "\copy (select * from  &table_name where &partition_con<'202009') to stdout"|psql -h &target_host -p &port_number -U &user_name -d &database_name -c "\copy  table_name from stdin" >> &table_name.log

将所有表全部拼写成上述类似的语句后,全部放在migrate_table.sh的脚本文件中,直接通过以下命令执行脚本即可:

代码语言:javascript
复制
nohup  migrate_table.sh &

需要注意的是,这种实际上还是执行了两个语句,一个是copy to,另一个是copy from。那么当源端copy出问题后,错误信息会在nohup.out中输出,如果目标端copy出问题后,则会在table_name.log中生成COPY 0的记录。这样,当某张表迁移出问题,就可以重新根据表名记录来确定并重新导入了。 最后,还有一个需要注意的点: COPY from命令在Postgersql中,会直接起一个事务。即当其中一条数据插入失败,整个事务就会回滚。所以只要有COPY 0的记录,可以在源端查一下是不是该表真的没有数据。如果是导入失败的,则该表可以直接重新导,里面不会有上次导入的数据记录。

墨天轮原文链接:https://www.modb.pro/db/29946

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据和云 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、背景
  • 三、迁移思路
  • 四、迁移步骤
    • 1. 表结构迁移
      • 2. 表数据迁移
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档