Oracle中应用场景 | Oracle中响应时间 | 迁移到Greenplum建议 |
分析型场景 | 1秒以上 | 此类应用完全可以迁移至Greenplum,迁移后性能会有较明显提升 |
并发小查询场景 | 1秒以内 | 并发小查询场景包括小表全表扫描和大表索引扫描场景,迁移至Greenplum性能在同一量级,但因为数据节点交互,延迟会略有增加 |
并发数据加载场景 | 1秒以内 | 可以迁移至Greenplum,需要将逐笔插入操作改为微批量插入,由于Greenplum MPP架构优势,加载性能会有较明显提升 |
低并发事务型场景 | 1秒以内 | 可以迁移至Greenplum,需要做适当业务改造,将逐笔操作改为微批量操作 |
高并发事务型场景 | 1秒以内 | 不建议迁移到Greenplum,由于数据跨节点的网络交互和锁的问题,会导致性能有较大的损失,甚至无法满足业务要求。请关注Greenplum的研发进展和新版本特性,Greenplum社区正在不断增强高并发事务型特性。 |
Oracle到Greenplum迁移场景建议
用ELT替代ETL
ETL做为构建数据仓库的重要一环,承担了数据从OLTP系统到OLAP系统时重要的抽取、转换和加载工作。在银行等金融用户中,一般都是通过在ETL服务器上部署专门的ETL工具(如informatic 或 datastage),将数据抽取到数据仓库中进行复杂分析计算,再将算好的结果推回到OLTP系统以完成在线处理。一般的处理流程如下图所示:

数据以T-1的方式加载到Greenplum集群
但受限于ETL服务器的硬件性能,在大数据量的抽取转换时性能较差,随着分布式数据库技术的发展和硬件计算能力的增强,越来越多的用户采用ELT的方式,将ETL中最为耗时的工作放在OLAP系统中完成。这样做,一方面减少了ETL服务器的工作负载,提升了数据的转换效率;另一方面可以利用数据库本身提供的丰富转换函数减少开发的工作量,如下图所示,转换过程可以在数据入库过程中完成,或者在库内完成,从而利用MPP并行的优势,提升转换性能。

ETL到ELT
对于由Oracle到Greenplum的迁移,我们也推荐使用ELT的方式进行处理,如下图所示:

使用ELT流程代替ETL
一、元数据迁移
关于AWS SCT安装和使用,可参考如下链接:
https://docs.aws.amazon.com/zh_cn/SchemaConversionTool/latest/userguide/Welcome.html

数据库存储对象转换评估报告

数据库代码转对象换评估报告
之后,我们点击应用就会在PostgreSQL数据库中创建对应的对象和函数。
SCT会自动作类型转换,如果你想了解Oracle和Greenplum中不同数据类型具体的转换方式,可以参考下表:
Oracle | Greenplum | Comment |
VARCHAR2(n) | VARCHAR(n) | 在Oracle中n代表的是字节数,在Greenplum中n代表的是字符数 |
CHAR(n) | CHAR(n) | 同上 |
NUMBER(n,m) | NUMERIC(n,m) | number可以转换成numeric,但如果真实业务中数值类型可以用:smallint、 int或bigint等替代,性能会有较大提升 |
NUMBER(4) | SMALLINT | |
NUMBER(9) | INT | |
NUMBER(18) | BIGINT | |
NUMBER(n) | NUMERIC(n) | 如果n>19,可以转换成numerice类型 |
DATE | TIMESTAMP(0) | Oracle和Greenplum都有日期类似,但Oracle的日期类型会同时保存日期和时间,而Greenplum只保包存日期 |
TIMESTAMP WITH LOCAL TIME ZONE | TIMESTAMPTZ | 注意:PostgreSQL中TIMESTAMPTZ不等同于Oralce中的TIMESTAMP WITH TIME ZONE |
CLOB | TEXT | PostgreSQL中TEXT类型不能超过1GB |
BLOB | BYTEA(1 GB limit) | 在Oracle中BLOB用于存放非结构化的二进制数据类型,BLOB最大可存储128TB,而PostgreSQL中BYTEA类型最大可以存储1GB,如果有更大的存储要求,可以使用Large object类型 |
Oracle和Greenplum的字段类型映射表
2) 通过pg_dump导出元数据,命令如下:
pg_dump -s -n pivotal >pivotal.sql
3) 在Greenplum中导入元数据,命令如下:
psql –af pivotal.sql >import.log 2>&1
注意:如果是正式的项目迁移,需要根据之前的介绍,确定每张表的分布键和存储方式。如果只是做POC验证,可以先通过如下命令修改数据库参数,保证数据库采用建议的存储方式充分打散,然后再用上面语句进行恢复:
ALTER DATABASE pivotal SET gp_create_table_random_default_distribution=on;ALTER DATABASE pivotal SET gp_default_storage_options='appendonly=true,blocksize=32768,compresstype=zlib,compresslevel=5,checksum=true,orientation=row';
此外,还应注意以下事项:
1)在Greenplum中,所有的用户表都需要指定分布键。如果Oracle中有明确的业务主键,可以设其为分布键。如果没有,可以选择区分度高的字段作为分布键或者将分布方式设为randomly。
2)在Greenplum中,如果因为业务需要指定主键字段,那么主键一定要包括分布键的所有字段,并与分布键定义的顺序一致,即如果主键是(a,b,c),那分布键可以是(a),(a,b),(a,b,c)。一般情况下,主键与分布键相同。
如下图所示,假如没有主键必须包括分布键的约束,c1作主键、c2作分布键。在插入第一条记录(1,2,3)时,根据c2字段计算哈希值后会把对应的记录存放在p1实例,在插入第二条记录(1,1,6)时,根据c2字段计算哈希值后会把对应的记录存放在p2实例。虽然在各个实例中,数据不会违反唯一性约束,但却违反了全局唯一性约束。

主键不包括分布键带来的问题
pivotal=# CREATE TABLE test2(a int,b int,primary key (b,a)) DISTRIBUTED BY (a);ERROR: PRIMARY KEY and DISTRIBUTED BY definitions incompatibleHINT: When there is both a PRIMARY KEY, and a DISTRIBUTED BY clause, the DISTRIBUTED BY clause must be equal to or a left-subset of the PRIMARY KEY
在即将发布的Greenplum6.0版本中,我们将该约束进一步放宽,只要分布键是主键的子集即可,在社区版本测试的结果如下图所示。

主键必须包括分布键

在函数中实现部分提交
执行后输出结果如下图所示。

部分提交的结果输出
注意:函数本质上还是一个事务,通过exception捕获,只是屏蔽掉了异常。让函数继续执行,如果在之后又遇见异常,前面做的操作仍然会被回滚,如下例所示。如果不将函数最外层的SQL封装在单独的begin……exception……end 块中,一旦出现异常,前面的操作仍然会被回滚。

外层SQL异常,导致前面操作回滚
解决办法就是拆成多个业务处理块,每个块中都做异常捕获,如下图所示。

拆成多个语句块,避免操作回滚
psql -af usr/local/greenplum-db/share/postgresql/contrib/dblink.sql

在函数中实现事务控制

函数没有结束,其他会话也能看到已经提交的事务
二、数据迁移
数据迁移包括全量和增量数据迁移。进行全量迁移时,可以用sqluldr2工具先把数据以csv的格式导出,然后再通过gpfdist加载到Greenplum中。当然,也可以使用命名管道或者可执行外部表实现数据的不落地加载。但在真实业务场景中,通过管道会造成数据从Oracle导出到Greenplum紧耦合,一旦网络出现问题,会出现pipe broken的错误。另外,真实场景中不同表的加载作业一般不会有依赖,在数据导出的同时也会有数据导入,实测之后使用管道和文件落地后再加载性能并无太大差异,所以真实业务场景中大都采用文件落地的方式再导入到Greenplum中。
增量迁移一般借助golden gate等cdc软件尽量做到数据的实时捕获,再通过gpfdist加载到Greenplum中。曾经有用户以250ms的间隔通过gpfdist实时加载数据到Greenplum中,在8个计算节点的集群上速度可达到200万/s。
1)在ETL服务器上部署sqludr和Oracle用户端,命令如下:
wget http://www.onexsoft.com/software/sqluldr2linux64.zipvi $ORACLE_HOME/network/admin/tnsnames.ora
2)将数据导成CSV文件,命令如下:
export NLS_LANG="SIMPLIFIED CHINESE"_CHINA.ZHS16GBKsqluldr2 user='username'/'password'@tnsname query="select /* parallel(2) */ * from PICCPROD.T_CONTRACT_MASTER" text=CSV file="/data/ods/test.dat" log=/tmp/sqluldr2.log
3)创建外部表进行数据导入,命令如下:
CREATEcreate EXTERNALexternal TABLEtable ext.t_contract_master_ext (like t_contract_master)LOCATION('gpfdist://10.111.224.1:9999/T_CONTRACT_MASTER.dat') FORMAT 'CSV' (HEADER delimiter as ',') ENCODING 'GB18030' LOG ERRORS SEGMENT REJECT LIMIT 2 rows;
数据导入过程中常见问题总结如下:
问题一:"invalid byte sequence for encoding "GB18030": 0x00"
原因:PostgreSQL不支持在文本字段中存储NULL(\ 0x00)字符
解决办法:删除掉NULL字符,即
for i in *.dat;do tr < $i -d '\000' > nonull/$i & <&-;done
/usr/local/bin/iconv -f GB18030 -t GB18030 --byte-subst="<0x%x>" T_CLAIM_CASE.dat
三、数据校验
接下来以Oracle迁移到Greenplum为例介绍一下数据比对的方法。
首先,分别往Oracle和Greenplum中插入Demo数据:
--oracle
CREATE TABLE hello(id int,name varchar2(32),price number);INSERT INTO hello SELECT rownum,rownum,rownum*0.1 FROM dual Connect By Rownum <= 100;
--greenplum
CREATE TABLE hello(id int,name varchar(32),price numeric);INSERT INTO hello select i,i,i*0.1 FROM generate_series(1,100) i;
以测试数据为例,分别在Oracle和Greenplum中执行以下命令:
select count(*) from hello;
输出结果分别如下图所示。

Oracle中输出count(*)结果

Greenplum中输出count(*)结果
方案2,数据条数比对+关键字段校验
select count(*),sum(price),avg(price) from hello;
执行结果分别如下图所示。

Oracle输出特征值结果

Greenplum中输出特征值结果
--Oracle
WITH foo AS (SELECT stragg(lower(standard_hash(id||name||to_char(price,'fm999999990.999999999'), 'MD5')) order by lower(standard_hash(id||name||to_char(price,'fm999999990.999999999'), 'MD5')) asc) AS total_md5 from hello ) SELECT lower(standard_hash(total_md5, 'MD5')) AS md5 FROM foo;
--Greenplum
WITH foo AS (select string_agg(md5(id||name||to_char(price,'fm999999990.999999999')) order by md5(id||name||to_char(price,'fm999999990.999999999')) asc) AS total_md5 FROM hello) SELECT md5(total_md5) AS md5 FROM foo;
执行结果分别如下图所示。

Oracle中全字段算md5结果

Greenplum中全字段算md5结果
通过查询数据字典,将上述过程封装成pl/pgsql和pl/sql过程语言,就可以实现表的md5值计算。其优点是对应用入侵较小,具有通用性,可以做全字段比对;缺点是需要对数据进行排序,比对过程较慢,即使使用Greenplum,最终md5值的计算也要汇总到master节点进行全局排序。而且,当表非常大时,string_agg会报内存不足的错误。另外,如果有中文等特殊字符,要求Oracle和Greenplum有相同的数据库端编码,否则显示出的字符看起来一致,但由于底层字节码不同,也会导致md5值不一样。
方案4,将每行计算出的md5值转换为4组数值类型,最终将sum后的结果作为整表的checksum值
由于md5是128位的十六进制编码,需要转换为4组32位编码的数值类型。通过这种方式,不需要进行全局排序,性能大大提升,另外也克服了string_agg和md5函数对内存使用的限制,但中文等特殊字符的编码限制仍然存在。
以测试数据为例,在Oracle下运行以下命令:
--Oracle
WITH foo AS (SELECT standard_hash(''||id||name||to_char(price,'fm999999990.999999999'),'MD5') AS hash FROM hello),foo1 as (SELECT sum(to_number(substr(hash, 1, 8),'xxxxxxxx')) AS hash_p1,sum(to_number(substr(hash, 9, 8),'xxxxxxxx')) AS hash_p2,sum(to_number(substr(hash, 17, 8),'xxxxxxxx')) AS hash_p3,sum(to_number(substr(hash, 25, 8),'xxxxxxxx')) AS hash_p4 FROM foo)SELECT lower(standard_hash(sum(hash_p1)||sum(hash_p2)||sum(hash_p3)||sum(hash_p4),'MD5')) AS md5 FROM foo1;
运行结果如下图所示。

Oracle中经过性能优化后算md5结果
在Greenplum下运行以下命令:
--Greenplum
WITHwith foo ASas (SELECTselect md5(''||id||name||to_char(price,'fm999999990.999999999')) ASas hash FROMfrom hello),foo1 ASas (SELECTselect sum(('x' || substring(hash, 1, 8))::bit(32)::bigint) ASas hash_p1,sum(('x' || substring(hash, 9, 8))::bit(32)::bigint) as hash_p2,sum(('x' || substring(hash, 17, 8))::bit(32)::bigint) ASas hash_p3,sum(('x' || substring(hash, 25, 8))::bit(32)::bigint) ASas hash_p4 FROMfrom foo)SELECTselect md5(''||sum(hash_p1)||sum(hash_p2)||sum(hash_p3)||sum(hash_p4)) ASas md5 FROMfrom foo1;
运行结果如下图所示。

Greenplum中经过性能优化后算md5结果
如果发现表的数据不一致,接下来要定位到具体的记录。当数据量非常大时,通过人工或外部程序进行比对都不太现实,这时候可以利用Greenplum强大的并行计算能力,通过两表关联来比对每行的md5值,找出具体的差异。
以测试数据为例,命令如下:
WITH oracle_checksum AS (SELECT id,md5(textin(record_out(foo2))) AS row_md5 FROM ora_hello AS foo2),gp_checksum AS (SELECT id,md5(textin(record_out(foo3))) AS row_md5 FROM gp_hello AS foo3),compare_result AS (SELECT oracle_checksum.id AS ora_id ,oracle_checksum.row_md5 AS ora_md5,gp_checksum.id AS gp_id,gp_checksum.row_md5 AS gp_md5 FROM oracle_checksum full outer join gp_checksum on oracle_checksum.row_md5=gp_checksum.row_md5)SELECT * FROM compare_result WHERE ora_md5 is null or gp_md5 is null;
总之,用户可以根据具体的业务要求和数据平台选择不同的数据比对方案。一般来说,选择方案2的用户较多,因为该方案不依赖底层数据平台,也不依赖具体数据库端的编码,数据一致性和性能都能得到较好的保证。但缺点是,在一些特殊情况下并不能保证数据完全一致,在Greenplum数据库中可以将方案4封装成如下的checksum函数来判断表中数据是否发生变化,从而扩展出更多的应用场景。 在8个计算节点的Greenplum真实用户环境下,计算1.5亿记录的checksum值只需要10秒。
CREATE OR REPLACE FUNCTION checksum(tablename character varying)RETURNS text AS$BODY$DECLAREv_tablename varchar := $1;v_md5 varchar;v_check_sql varchar;BEGINv_check_sql := $$with foo as (select md5(textin(record_out(foo2))) as hash FROM $$||v_tablename ||$$ as foo2), foo1 as (select sum(('x' || substring(hash, 1, 8))::bit(32)::bigint) as hash_p1,sum(('x' || substring(hash, 9, 8))::bit(32)::bigint) as hash_p2,sum(('x' || substring(hash, 17, 8))::bit(32)::bigint) as hash_p3,sum(('x' || substring(hash, 25, 8))::bit(32)::bigint) as hash_p4 from foo) select md5(''||sum(hash_p1)||sum(hash_p2)||sum(hash_p3)||sum(hash_p4)) from foo1;$$;EXECUTE v_check_sql INTO v_md5;RETURN v_md5;END;$BODY$LANGUAGE 'plpgsql' immutable ;
下周,我们将在第三篇中,为大家分析Oracle迁移到Greenplum的特殊场景。
本文摘录自Greenplum官方教材《Greenplum:从大数据战略到实现》,点击阅读原文获取购买链接。





