YuGong ORACLE迁移到MYSQL(单表对单表)
最近,发现了个阿里开源迁移利器yugong,针对于O-M以及O-O,然后抽空搞了单表对单表的迁移实验,ORACLE中ORCL数据库两张表分别迁移到MYSQL中的TEST以及MARVIN数据库中,可以简单认为一库对多库,其实MYSQL中库的概念是对于ORACLE中SCHEMA概念..
所以,这里针对于ORACLE全库迁移到MYSQL,就需要配置参数文件,yugong.table.white不指定迁移表,再根据需求,配置translator目录下的DataTranslator,再去目标库中手动创建空表结构,最后开启传输应用.
整个数据流为:DB -> Extractor -> DataTranslator -> Applier -> DB
环境要求
操作系统
1、有bat和shell,支持windows/LInux
2、jdk建议使用1.6.25以上版本源端、目标端操作执行: 操作系统下载安装jdk- 数据库
1、源库为ORACLE,目标库可谓mysql/oracle/drds,无数据库版本要求
2、需要数据库账户权限2.1、源库(ORACLE) CREATE USER yugong IDENTIFIED BY yugong; GRANT CONNECT,RESOURCE TO yugong; GRANT SELECT,INSERT,UPDATE,DELETE ON marvin.marvin TO yugong; #注意,这里是授予某个用户下某张表的权限....若是配置参数中白名单需要迁移的表是整个数据库,则这个某张表权限执行语句太多. 则可直接授予DBA权限 GRANT DBA TO yugong; GRANT CREATE ANY MATERIALIZED VIEW TO yugong; GRANT DROP ANY MATERIALIZED VIEW TO yugong; 2.2、目标库(mysql/oracle) 当前目标环境Mysql(root用户) create user 'yugong'@'%' identified by 'yugong'; create user 'yugong'@'localhost' identified by 'yugong'; GRANT SELECT,INSERT,UPDATE,DELETE ON `test`.* TO 'yugong'@'%'; GRANT SELECT,INSERT,UPDATE,DELETE ON `test`.* TO 'yugong'@'localhost'; GRANT SELECT,INSERT,UPDATE,DELETE ON `marvin`.* TO 'yugong'@'%'; GRANT SELECT,INSERT,UPDATE,DELETE ON `marvin`.* TO 'yugong'@'localhost'; flush privileges; 迁移方案
迁移步骤分两步1、全量迁移 2、增量迁移过程描述:
1、增量数据收集(创建oracle表的增量物化视图) 2、全量复制 3、增量复制(并行数据校验) 4、原库停写,切换到新库回滚方案:
开启新库到老库的数据回流部署
- 下载地址 https://github.com/alibaba/yugong/releases
源端、目标端执行: groupadd yugong useradd -g yugong yugong passwd yugong mkdir /yugong chown yugong:yugong /yugong cd /yugong tar -xvf yugong-1.0.3.tar.gz 上传解压 - 目录结构
drwxrwxr-x 5 yugong yugong 174 Dec 4 13:50 yugong-1.0.3 -rwxrwxrwx 1 yugong yugong 2196798 Apr 12 09:15 yugong-1.0.3.tar.gz 创建测试数据
源端
// Oracle表 marvin用户下: create table oracle1 ( id NUMBER(11) , name varchar2(32) , alias_name char(32) default ' ' not null, amount number(11,2), score number(20), text_b blob, text_c clob, gmt_create date not null, gmt_modified date not null, CONSTRAINT yugong_example_oracle_pk_id PRIMARY KEY (id) ); scott用户下: create table oracle2 ( id NUMBER(11) , name varchar2(32) , alias_name char(32) default ' ' not null, amount number(11,2), score number(20), text_b blob, text_c clob, gmt_create date not null, gmt_modified date not null, CONSTRAINT yugong_example_oracle_pk_id PRIMARY KEY (id) );目标端
// mysql表 test数据库: create table test.mysql1 ( id bigint(20) unsigned auto_increment, display_name varchar(128) , amount varchar(32), score bigint(20) unsigned , text_b blob, text_c text, gmt_create timestamp not null, gmt_modified timestamp not null, gmt_move timestamp not null, CONSTRAINT yugong_example_mysql_pk_id PRIMARY KEY (id) ); marvin数据库: create table marvin.mysql2 ( id bigint(20) unsigned auto_increment, display_name varchar(128) , amount varchar(32), score bigint(20) unsigned , text_b blob, text_c text, gmt_create timestamp not null, gmt_modified timestamp not null, gmt_move timestamp not null, CONSTRAINT yugong_example_mysql_pk_id PRIMARY KEY (id) );
- 下载地址 https://github.com/alibaba/yugong/releases
修改配置
配置文件路径:/yugong/conf/yugong.properties
正常情况下,只需修改下yugong.database的源库和目标库的地址信息,通过yugong.table.white定义本次需要迁移的表,通过yugong.table.mode定义要执行的操作,是全量还是增量等,其他的可以使用默认值,关于配置参数详解以及其他步骤,可参考文档链接:
https://github.com/alibaba/yugong/wiki/AdminGuide?spm=a2c4e.11153940.blogcont7937.5.a0c079e0wrsNJa
配置如下:注意:该yugong软件只需要在源端或者目标端安装一个软件即可,无需两个都安装…刚开始捯饬半天,才发现…
yugong.database.source.username=yugong yugong.database.source.password=yugong yugong.database.source.type=ORACLE yugong.database.source.url=jdbc:oracle:thin:@//172.41.176.122:1521/orcl yugong.database.source.encode=UTF-8 yugong.database.source.poolSize=30 yugong.database.target.url=jdbc:mysql://172.41.176.114:3306/test yugong.database.target.url=jdbc:mysql://172.41.176.114:3306/marvin 可以指定一张表对应一个数据库mysql数据库 yugong.database.target.username=yugong yugong.database.target.password=yugong yugong.database.target.type=MYSQL yugong.database.target.encode=UTF-8 yugong.database.target.poolSize=30 yugong.table.white=marvin.oracle1,scott.oracle2 yugong.table.mode=ALL yugong.extractor.dump=true --这里DUMP出捕获日志,以便查看源端是否正常捕获数据,默认是false yugong.applier.dump=true --这里DUMP出应用日志,以便查看目标端是否应用成功,默认false数据转换逻辑配置说明
如果要迁移的oracle和mysql的表结构不同,比如表名,字段名有差异,字段类型不兼容,需要使用自定义数据转换。如果完全相同那就可以跳过此章节,越过自定数据类型转换(一般指是Oracle 迁移到 Oracle才可能不需要数据转换) 整个数据流为:DB -> Extractor -> DataTranslator -> Applier -> DB,程序预留DataTranslator接口,允许外部用户自定义数据处理逻辑,比如: 1. 表名不同 2. 字段名不同 3. 字段类型不同 4. 字段个数不同 5. 运行过程join其他表的数据做计算等 说明 1.DataTranslator目前仅支持java扩展,允许用户完成类实现后,将类源文件放置到conf/translator/目录下,yugong启动后会进行动态编译. 2.DataTranslator目前查找规则会根据表名自动查找,比如需要处理的表为dbname.test_all_one_pk,查找的时候会将test_all_one_pk转化为TestAllOnePk + 固定DataTranslator后缀. (如果当前classpath中存在,优先使用classpath,如果不存在,则到conf/translator中查找该名字的java文件进行动态编译) 3.目前提供了几个样例,可参见解压后的/yugong/yugong-master/src/test/java/com/taobao/yugong/translator目录 a. YugongExampleOracleDataTranslator (当前例子,介绍oracle一张表和mysql一张表之间的转换处理) b. YugongExampleJoinDataTranslator (介绍oracle多张表和mysql一张表之间的转换处理,oracle中会通过一张表为主表,运行时join查询出其他表数据,合并同步到mysql) c. YugongExampleTwoDataTranslator (介绍oracle一张表和mysql多张表之间的转换处理,oracle的一张大表数据,可运行时拆分后输出到多张mysql表上) d.YugongExampleSimpleDataTranslator.java (一个简易转换逻辑的写法,包含常用的转换处理) 综上,如果源端和目标端的表存在表名称、字段名、数据类型、字段个数等不同,则迁移需要配置表名称对应的DataTranslator,也就是有多少张存在不同的表相应就需要配置多少个DataTranslator。如果懂java的同学可以通过类的继承(实现同一类型的不同选项)的配置- 自定义数据转换
上文准备的测试环境的源端oracle的表oracle1以及oracle2和对应目标端mysql oracle1以及oracle2的表有如下不同的地方: 1. table名不同. oracle中为oracle1、oracle2,mysql中为mysql1、mysql2 2. 字段名字不同. oracle中的name字段,映射到mysql的display_name 3. 字段逻辑处理. mysql的display_name字段数据来源为oracle库的:name+'('alias_name+')' 4. 字段类型不同. oracle中的amount为number类型,映射到mysql的amount为varchar文本型 5. 源库多一个字段. oracle中多了一个alias_name字段 6. 目标库多了一个字段.mysql中多了一个gmt_move字段,(用迁移时的当前时间进行填充) 故,需要根据不同的地方定义DataTranslator.java,下文的YugongExampleADataTranslator.java中配置这里的6项不同之处 将类源文件存放于conf/translator目录下,编辑添加传输类文件(可根据之前的YugongExampleOracleDataTranslator文件进行配置修改对应部分)
根据DataTranslator目前查找规则会根据表名自动查找
#源库oracle的表为Oracle1,故对应conf/translator/Oracle1ADataTranslator.java
[yugong@rac2 translator]$ vi Oracle1DataTranslator.java
package com.taobao.yugong.translator;
import java.sql.Types;
import java.util.Date;
import org.apache.commons.lang.ObjectUtils;
import com.taobao.yugong.common.db.meta.ColumnMeta;
import com.taobao.yugong.common.db.meta.ColumnValue;
import com.taobao.yugong.common.model.record.Record;
public class Oracle1DataTranslator extends AbstractDataTranslator implements DataTranslator {
public boolean translator(Record record) {
// 1. schema/table名不同
record.setSchemaName("test");
record.setTableName("mysql1");
//至此可知道源表为oracle1,目标表为mysql1
// 2. 字段名字不同,
ColumnValue nameColumn = record.getColumnByName("name");
if (nameColumn != null) {
nameColumn.getColumn().setName("display_name");
}
// 3. 字段逻辑处理
//源表的name字段,目标表中叫display_name
ColumnValue aliasNameColumn = record.getColumnByName("alias_name");
if (aliasNameColumn != null) {
StringBuilder displayNameValue = new StringBuilder(64);
displayNameValue.append(ObjectUtils.toString(nameColumn.getValue()))
.append('(')
.append(ObjectUtils.toString(aliasNameColumn.getValue()))
.append(')');
nameColumn.setValue(displayNameValue.toString());
}
// 4. 字段类型不同
ColumnValue amountColumn = record.getColumnByName("amount");
if (amountColumn != null) {
amountColumn.getColumn().setType(Types.VARCHAR);
amountColumn.setValue(ObjectUtils.toString(amountColumn.getValue()));
}
// 5. 源库多一个字段
record.removeColumnByName("alias_name");
// 6. 目标库多了一个字段
ColumnMeta gmtMoveMeta = new ColumnMeta("gmt_move", Types.TIMESTAMP);
ColumnValue gmtMoveColumn = new ColumnValue(gmtMoveMeta, new Date());
gmtMoveColumn.setCheck(false);// 该字段不做对比
record.addColumn(gmtMoveColumn);
// ColumnValue text_c = record.getColumnByName("text_c");
// try {
// text_c.setValue(new String((byte[]) text_c.getValue(), "GBK"));
// } catch (UnsupportedEncodingException e) {
// e.printStackTrace();
// }
return super.translator(record);
}
}
[yugong@rac2 translator]$ vi Oracle2DataTranslator.java
package com.taobao.yugong.translator;
import java.sql.Types;
import java.util.Date;
import org.apache.commons.lang.ObjectUtils;
import com.taobao.yugong.common.db.meta.ColumnMeta;
import com.taobao.yugong.common.db.meta.ColumnValue;
import com.taobao.yugong.common.model.record.Record;
public class Oracle2DataTranslator extends AbstractDataTranslator implements DataTranslator {
public boolean translator(Record record) {
// 1. schema/table名不同
record.setSchemaName("marvin");
record.setTableName("mysql2");
// 2. 字段名字不同
ColumnValue nameColumn = record.getColumnByName("name");
if (nameColumn != null) {
nameColumn.getColumn().setName("display_name");
}
// 3. 字段逻辑处理
ColumnValue aliasNameColumn = record.getColumnByName("alias_name");
if (aliasNameColumn != null) {
StringBuilder displayNameValue = new StringBuilder(64);
displayNameValue.append(ObjectUtils.toString(nameColumn.getValue()))
.append('(')
.append(ObjectUtils.toString(aliasNameColumn.getValue()))
.append(')');
nameColumn.setValue(displayNameValue.toString());
}
// 4. 字段类型不同
ColumnValue amountColumn = record.getColumnByName("amount");
if (amountColumn != null) {
amountColumn.getColumn().setType(Types.VARCHAR);
amountColumn.setValue(ObjectUtils.toString(amountColumn.getValue()));
}
// 5. 源库多一个字段
record.removeColumnByName("alias_name");
// 6. 目标库多了一个字段
ColumnMeta gmtMoveMeta = new ColumnMeta("gmt_move", Types.TIMESTAMP);
ColumnValue gmtMoveColumn = new ColumnValue(gmtMoveMeta, new Date());
gmtMoveColumn.setCheck(false);// 该字段不做对比
record.addColumn(gmtMoveColumn);
// ColumnValue text_c = record.getColumnByName("text_c");
// try {
// text_c.setValue(new String((byte[]) text_c.getValue(), "GBK"));
// } catch (UnsupportedEncodingException e) {
// e.printStackTrace();
// }
return super.translator(record);
}
}
- 启停
几点注意:su - yugong mkdir /yugong/yugong-master/src/main/conf/ cd /yugong/yugong-master/src/main/conf/ cp /yugong/yugong-master/src/main/resources/* . cd /yugong/yugong-master/src/main/bin [yugong@rac2 bin]$ sh startup.sh [yugong@rac2 bin]$ ls startup.bat startup.sh stop.sh yugong.pid- linux启动完成后,会在bin目录下生成yugong.pid,stop.sh会读取yugong.pid进行进程关闭
- startup.sh默认读取系统环境变量中的which java获得JAVA执行路径,需要设置PATH=$JAVA_HOME/bin环境变量
插入测试数据
源端:marvin用户 SQL> insert into oracle1 values(1,'ljh','agapple',10.2,100, NULL , NULL ,sysdate,sysdate); insert into oracle1 values(2,'yugong','yugong',16.88,2088, NULL , NULL ,sysdate,sysdate); insert into oracle1 values(3,'yugong','yugong',16.88,2088, NULL , NULL ,sysdate,sysdate); insert into oracle1 values(4,'yugong','yugong',16.88,2088, NULL , NULL ,sysdate,sysdate) commit; scott用户: SQL> insert into oracle2 values(1,'ljh','agapple',10.2,100, NULL , NULL ,sysdate,sysdate); insert into oracle2 values(2,'yugong','yugong',16.88,2088, NULL , NULL ,sysdate,sysdate); insert into oracle2 values(3,'yugong','yugong',16.88,2088, NULL , NULL ,sysdate,sysdate); insert into oracle2 values(4,'yugong','yugong',16.88,2088, NULL , NULL ,sysdate,sysdate); commit; 捕获日志输出示例如下 - Schema: MARVIN , Table: ORACLE1 , Type: I ----------------- ---Pks ColumnValue [column=ColumnMeta[name=ID,type=3], value=4] ---Columns ColumnValue [column=ColumnMeta[name=ALIAS_NAME,type=1], value=yugong ] ColumnValue [column=ColumnMeta[name=GMT_CREATE,type=93], value=2018-04-19 14:35:44.0] ColumnValue [column=ColumnMeta[name=SCORE,type=3], value=2088] ColumnValue [column=ColumnMeta[name=AMOUNT,type=3], value=16.88] ColumnValue [column=ColumnMeta[name=NAME,type=12], value=yugong] ColumnValue [column=ColumnMeta[name=TEXT_B,type=2004], value=null] ColumnValue [column=ColumnMeta[name=TEXT_C,type=2005], value=null] ColumnValue [column=ColumnMeta[name=GMT_MODIFIED,type=93], value=2018-04-19 14:35:44.0] ---END **************************************************** 应用日志实例如下 - Schema: test , Table: mysql1 , Type: I ----------------- ---Pks ColumnValue [column=ColumnMeta[name=ID,type=3], value=4] ---Columns ColumnValue [column=ColumnMeta[name=GMT_CREATE,type=93], value=2018-04-19 14:35:44.0] ColumnValue [column=ColumnMeta[name=SCORE,type=3], value=2088] ColumnValue [column=ColumnMeta[name=AMOUNT,type=12], value=16.88] ColumnValue [column=ColumnMeta[name=DISPLAY_NAME,type=12], value=yugong(yugong )] ColumnValue [column=ColumnMeta[name=TEXT_B,type=2004], value=null] ColumnValue [column=ColumnMeta[name=TEXT_C,type=2005], value=null] ColumnValue [column=ColumnMeta[name=GMT_MODIFIED,type=93], value=2018-04-19 14:35:44.0] ColumnValue [column=ColumnMeta[name=GMT_MOVE,type=93], value=Thu Apr 19 14:35:47 CST 2018] ---END **************************************************** 目标端验证: 验证源端marvin用户插入: mysql> use test; Database changed mysql> select * from mysql1 -> ; +----+------------------------------------------+--------+-------+--------+--------+---------------------+---------------------+---------------------+ | id | display_name | amount | score | text_b | text_c | gmt_create | gmt_modified | gmt_move | +----+------------------------------------------+--------+-------+--------+--------+---------------------+---------------------+---------------------+ | 1 | ljh(agapple ) | 10.2 | 100 | NULL | NULL | 2018-04-19 10:45:15 | 2018-04-19 10:45:15 | 2018-04-19 10:57:02 | | 2 | yugong(yugong ) | 16.88 | 2088 | NULL | NULL | 2018-04-19 10:45:15 | 2018-04-19 10:45:15 | 2018-04-19 10:57:02 | | 3 | yugong(yugong ) | 16.88 | 2088 | NULL | NULL | 2018-04-19 10:58:18 | 2018-04-19 10:58:18 | 2018-04-19 10:58:22 | | 4 | yugong(yugong ) | 16.88 | 2088 | NULL | NULL | 2018-04-19 14:35:44 | 2018-04-19 14:35:44 | 2018-04-19 14:35:47 | +----+------------------------------------------+--------+-------+--------+--------+---------------------+---------------------+---------------------+ 4 rows in set (0.00 sec) 验证源端scott用户插入: mysql> use marvin; Database changed mysql> select * from mysql2; +----+------------------------------------------+--------+-------+--------+--------+---------------------+---------------------+---------------------+ | id | display_name | amount | score | text_b | text_c | gmt_create | gmt_modified | gmt_move | +----+------------------------------------------+--------+-------+--------+--------+---------------------+---------------------+---------------------+ | 1 | ljh(agapple ) | 10.2 | 100 | NULL | NULL | 2018-04-19 14:39:08 | 2018-04-19 14:39:08 | 2018-04-19 14:39:08 | | 2 | yugong(yugong ) | 16.88 | 2088 | NULL | NULL | 2018-04-19 14:39:08 | 2018-04-19 14:39:08 | 2018-04-19 14:39:08 | | 3 | yugong(yugong ) | 16.88 | 2088 | NULL | NULL | 2018-04-19 14:39:08 | 2018-04-19 14:39:08 | 2018-04-19 14:39:08 | | 4 | yugong(yugong ) | 16.88 | 2088 | NULL | NULL | 2018-04-19 14:39:08 | 2018-04-19 14:39:08 | 2018-04-19 14:39:08 | +----+------------------------------------------+--------+-------+--------+--------+---------------------+---------------------+---------------------+ 4 rows in set (0.00 sec)日志查看
对应日志结构为: logs/ - yugong/ #系统根日志 - table.log - ${table}/ #每张同步表的日志信息 - table.log - extractor.log - applier.log - check.log 具体日志介绍,参考地址:https://github.com/alibaba/yugong/wiki/AdminGuide?spm=a2c4e.11153940.blogcont7937.5.a0c079e0wrsNJa 中的查看日志页面- 切换流程
1、当任务处于追上状态时候,表示已经处于实时同步状态 2、后续通过源数据库进行停写,稍等1-2分钟后(保证延时的数据最终得到同步,此时源库和目标库当前数据是完全一致的) 3、检查增量持续处于NO_UPDATE状态,可关闭该迁移任务(sh stop.sh),即可发布新程序,使用新的数据库,完成切换的流程. 运行模式详解
- MARK模式
MARK模式(MARK) 开启增量日志的记录,如果是oracle就是创建物化视图 - CLEAR模式
CLEAR模式(CLEAR) 清理增量日志的记录,如果是oracle就是删除物化视图 全量模式(FULL)
全量模式(FULL) 全量模式,顾名思议即为对源表进行一次全量操作,遍历源表所有的数据后,插入目标表. 全量有两种处理方式: 分页处理:如果源表存在主键,只有一个主键字段,并且主键字段类型为Number类型,默认会选择该分页处理模式. 优点:支持断点续做,对源库压力相对较小。 缺点:迁移速度慢 once处理:通过select * from访问整个源表的某一个mvcc版本的数据,通过cursor.next遍历整个结果集. 优点:迁移速度快,为分页处理的5倍左右。 缺点:源库压力大,如果源库并发修改量大,会导致数据库MVCC版本过多,出现栈错误. 还有就是不支持断点续做. 特别注意 如果全量模式运行过程中,源库有变化时,不能保证源库最近变化的数据能同步到目标表,这时需要配合增量模式. 具体操作就是:在运行全量模式之前,先开启增量模式的记录日志功能,然后开启全量模式,完成后,再将最近变化的数据通过增量模式同步到目标表增量模式(INC)
全量模式,顾名思议即为对源表增量变化的数据插入目标表,增量模式依赖记录日志功能. 目前增量模式的记录日志功能,是通过oracle的物化视图功能。 创建物化视图 CREATE MATERIALIZED VIEW LOG ON ${tableName} with primary key. 运行增量模式之前,需要先开启记录日志的功能,即预先创建物化视图. 特别是配合全量模式时,创建物化视图的时间点要早于运行全量之前,这样才可以保证数据能全部同步到目标表 增量模式没有完成的概念,它只有追上的概念,具体的停止需有业务进行判断,可以看一下切换流程- 自动模式(ALL)
自动模式(ALL) 自动模式,是对全量+增量模式的一种组合,自动化运行,减少操作成本. 自动模式的内部实现步骤: 开启记录日志功能. (创建物化视图) 运行全量同步模式. (全量完成后,自动进入下一步) 运行增量同步模式. (增量模式,没有完成的概念,所以也就不会自动退出,需要业务判断是否可以退出,可以看一下切换流程) - 对比模式(CHECK)
毫无疑问,ALL 自动模式可以节省很多人工,迁移首选,自动全+增……对比模式(CHECK) 对比模式,即为对源库和目标库的数据进行一次全量对比,验证一下迁移结果. 对比模式为一种可选运行,做完全量/增量/自动模式后,可选择性的运行对比模式,来确保本次迁移的正确性.
- MARK模式
版权声明:本文为博主原创文章,未经博主允许不得转载。



