文章目录
  1. 1. 配置 MyCAT 连接 Oracle
  2. 2. Oracle 数据导出
  3. 3. 导入 MyCAT
  4. 4. 后记

MyCAT 的应用场景很多,很多应用在数据路由场景,将许多异构的数据库放在一起,使用 MyCAT 做 sql 转发。由于有这样的特性,MyCAT 在去 IOE 中也有比较丰富的应用。例如,将 Oracle 中的数据逐步的迁移到 mysql 中,在逐步迁移的过程中,仍然能保持几乎不间断的对外访问。下面距举例说明。

配置 MyCAT 连接 Oracle

数据迁移的第一步就是使用 MyCAT 作为中间件隔离 Oracle,这其中可能涉及到部分 sql 和应用的改写。配置如下,

1
2
3
4
5
6
7
8
9
<dataNode name="oradn0" dataHost="oracle1" database="scott" />
<dataHost name="oracle1" maxCon="1000" minCon="1" balance="0" writeType="0" dbType="oracle"
dbDriver="jdbc">
<heartbeat>select 1 from dual</heartbeat>
<connectionInitSql>alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss'</connectionInitSql>
<writeHost host="hostM1" url="jdbc:oracle:thin:@192.168.2.202:1521:orcl" user="scott" password="tiger" >
</writeHost>
</dataHost>

完成 MyCAT 的配置之后,应用程序将看不到 Oracle,后续的数据迁移对应用是透明的,此时,架构图如下,

假设有 Oracle 中有3张表,用户表(c_user)、交易表(c_order)、转账表(c_transfer),需要迁移用户表和交易表到 mysql 中。这3张表在当前的 MyCAT 中可以做如下配置,

1
2
3
<table name="c_user" primaryKey="ID" type="global" dataNode="oradn0" />
<table name="c_order" primaryKey="ID" type="global" dataNode="oradn0" />
<table name="c_transfer" primaryKey="ID" type="global" dataNode="oradn0" />

Oracle 数据导出

假设 Oracle 中,上述3张表的建表语句如下,

1
2
3
create table c_user(id number(20) primary key, user_name varchar(20), passwd varchar(20), nick_name varchar(40));
create table c_order(id number(20) primary key, user_id number(20), payment number);
create table c_transfer(id number(20) primary key, from_user_id number(20), to_user_id number(20), amount number);

为了便于演示,测试数据库中的数据量比较小,每个表只有1000条,不过这不影响操作结果。另外,为了测试迁移中中文编码可能出现的问题,部分字段使用了中文,数据库字符集是 GBK,如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
SQL> select count(*) from c_user;
COUNT(*)
----------
1000
SQL> select * from c_user where rownum<=5;
ID USER_NAME PASSWD NICK_NAME
---------- ---------- ---------- ----------
1000 a1000 111111
1001 a1001 111111
1002 a1002 111111
1003 a1003 111111
1004 a1004 111111
SQL> select count(*) from c_order;
COUNT(*)
----------
1000
SQL> select * from c_order where rownum<=5;
ID USER_ID PAYMENT
---------- ---------- ----------
10000 1000 100
10001 1001 100
10002 1002 100
10003 1003 100
10004 1004 100
SQL> select * from nls_database_parameters where parameter = 'NLS_CHARACTERSET';
PARAMETER VALUE
------------------------- --------------------
NLS_CHARACTERSET ZHS16GBK

在生产环境中,应该小步前进,对于数据比较多的表,逐个迁移(如果 join 的 sql 不多的话)。实验中,我们采用一次迁移两张表的做法。

将数据从 Oracle 中导出,采用 spool 方法,这主要是方便 MyCAT 后续导入,sqlplus 相关语句如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SET HEADING OFF
SET ECHO OFF
SET FEEDBACK OFF
SET PAGES 0
SET LINESIZE 32766
SET LONG 1999999
SET TRIMOUT ON
SET TRIMSPOOL ON
SET NEWPAGE NONE
SET SQLBLANKLINES OFF
SET TRIMS ON
SET TIMING OFF
SET SERVEROUTPUT ON
SET VERIFY OFF
SPOOL c_user.dat
SELECT '"'||id||'"|"'||user_name||'"|"'||passwd||'"|"'||nick_name||'"' FROM c_user;
spool off
SPOOL c_order.dat
SELECT '"'||id||'"|"'||user_id||'"|"'||payment||'"' FROM c_order;
spool off

用这样的方法,导出的数据会比较整齐,而且连字符集也统一处理了(只要 sqlplus 的客户端字符集配置的对),如下,

同样的方法可以导出另一个表的数据,在此不再赘述。

导入 MyCAT

首先,先要在 MyCAT 中配置将要导入的数据库和表,假设导入的数据要分片到10个 mysql 数据库中,可以如下配置,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dataNode name="dn0" dataHost="localhost1" database="dbtest0" />
<dataNode name="dn1" dataHost="localhost1" database="dbtest1" />
<dataNode name="dn2" dataHost="localhost1" database="dbtest2" />
<dataNode name="dn3" dataHost="localhost1" database="dbtest3" />
<dataNode name="dn4" dataHost="localhost1" database="dbtest4" />
<dataNode name="dn5" dataHost="localhost1" database="dbtest5" />
<dataNode name="dn6" dataHost="localhost1" database="dbtest6" />
<dataNode name="dn7" dataHost="localhost1" database="dbtest7" />
<dataNode name="dn8" dataHost="localhost1" database="dbtest8" />
<dataNode name="dn9" dataHost="localhost1" database="dbtest9" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="workstation:3306" user="root"
password="111111">
</writeHost>
</dataHost>

迁移的2张表采用 id 取余数算法分片,另外的表不做变动,如下配置,

1
2
3
<table name="c_user" dataNode="dn0,dn1,dn2,dn3,dn4,dn5,dn6,dn7,dn8,dn9" rule="mod-10" />
<table name="c_order" dataNode="dn0,dn1,dn2,dn3,dn4,dn5,dn6,dn7,dn8,dn9" rule="mod-10" />
<table name="c_transfer" primaryKey="ID" type="global" dataNode="oradn0" />

注:此处采用简便的分片方法,实际上,这里应该使用ER分片会更好。参考另一篇文章《MyCAT 分片》。

然后,重启 MyCAT,或者使用管理端的 reload @@config_all,使用如下语句建表,

1
2
create table c_user(id bigint primary key, user_name varchar(20), passwd varchar(20), nick_name varchar(40));
create table c_order(id bigint primary key, user_id bigint, payment decimal);

MyCAT 支持类似 mysql 的 load data,而且支持在导入的过程中完成数据路由,操作如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$ mysql -hworkstation -utest -DTESTDB -P8066 -ptest --local-infile=1
mysql> SET NAMES 'utf8';
Query OK, 0 rows affected (0.00 sec)
mysql> SHOW VARIABLES LIKE 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)
mysql> load DATA local INFILE '/home/arnes/project/c_user.dat' IGNORE INTO TABLE c_user CHARACTER SET 'utf8' FIELDS TERMINATED BY '|' ENCLOSED BY '"' LINES TERMINATED BY '\n' (id, user_name, passwd, nick_name) ;
Query OK, 1000 rows affected (0.16 sec)
Records: 1000 Deleted: 0 Skipped: 0 Warnings: 0
mysql> select * from c_user limit 5;
+------+-----------+--------+-----------+
| id | user_name | passwd | nick_name |
+------+-----------+--------+-----------+
| 1001 | a1001 | 111111 | 王 |
| 1011 | a1011 | 111111 | 王 |
| 1021 | a1021 | 111111 | 王 |
| 1031 | a1031 | 111111 | 王 |
| 1041 | a1041 | 111111 | 王 |
+------+-----------+--------+-----------+
5 rows in set (0.00 sec)
mysql> select count(*) from c_user;
+--------+
| COUNT0 |
+--------+
| 1000 |
+--------+
1 row in set (0.09 sec)

其中,有一些地方需要特别注意。

  1. load data 命令中的 character set 参数,指定文件的编码,建议跟 mysql 的编码一致,否则可能乱码。其中字符集编码必项用单引号,否则会解析出错。因为我用的文件在导出的时候已经完成转码,因此这里选择 ‘utf8’;
  2. 根据 MyCAT 和 mysql 官方文档,如果用 mysql 客户端登陆执行 load data,必须在 mysql 客户端登陆时增加参数 --local-infile=1,否则不能导入,而 jdbc 不需要加这个参数(我估计没人用 jdbc 做这样的操作);
  3. MyCAT 的 load data 必须要指定导入的列名放在命令的最后,列名的顺序与数据要一致,而且必须要包含分片字段,如上面的 (id, user_name, passwd, nick_name)
  4. 登陆 MyCAT 后需要检查 mysql 客户端和服务器端的字符集是否相同,如果不同,使用 SET NAMES 'utf8';,将客户端字符集统一设置成 utf-8,如下,

与上面相同的方法,可以导入另一张表,如下,

每个分片上有100条数据,如下,

至此,MyCAT 已经可以将数据路由到新的 mysql 服务器上,迁移完成。

后记

如果不常用 mysql 的人,做 load data 应该会碰到挺多问题,在加上 MyCAT,估计一下很难发现问题,比如下面这个问题,

1
2
3
4
5
6
7
8
mysql> load DATA local INFILE '/home/arnes/project/c_user-10.dat' IGNORE INTO TABLE c_user FIELDS TERMINATED BY '|' ENCLOSED BY '"' LINES TERMINATED BY '\n' (id, user_name, passwd, nick_name) ;
ERROR 2027 (HY000): Malformed packet
mysql> load DATA local INFILE '/home/arnes/project/c_user-10.dat' IGNORE INTO TABLE c_user CHARACTER SET 'utf8' FIELDS TERMINATED BY '|' ENCLOSED BY '"' LINES TERMINATED BY '\n' (id, user_name, passwd, nick_name) ;
^CCtrl-C -- sending "KILL QUERY 24" to server ...
Ctrl-C -- query aborted.
^CCtrl-C -- sending "KILL 24" to server ...
Ctrl-C -- query aborted.
ERROR 2013 (HY000): Lost connection to MySQL server during query

从字面上感觉是数据格式出错了,实际上是 mysql 客户端登陆的时候没有加 --local-infile=1参数,而且这个问题会导致 mysql 客户端卡死,并断掉连接。还有,如果导入的时候没有加列名,也会提示很诡异的错误,

1
2
3
4
5
6
7
8
9
mysql> load DATA local INFILE '/home/arnes/project/c_user-10.dat' IGNORE INTO TABLE c_user FIELDS TERMINATED BY '|' ENCLOSED BY '"' LINES TERMINATED BY '\n';
ERROR 2013 (HY000): Lost connection to MySQL server during query
mysql> load DATA local INFILE '/home/arnes/project/c_user-10.dat' IGNORE INTO TABLE c_user FIELDS TERMINATED BY '|' ENCLOSED BY '"' LINES TERMINATED BY '\n';
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id: 40
Current database: TESTDB
ERROR 2013 (HY000): Lost connection to MySQL server during query

这个错误也会导致断掉客户端连接,甚至还会打印很多类似这样的日志,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
03/27 14:11:55.151 WARN [$_NIOREACTOR-2-RW] (ServerConnection.java:209) -ServerConnection [id=40, schema=TESTDB, host=192.168.2.201, user=test,txIsolation=3, autocommit=true, schema=TESTDB]/*loaddata*/insert into C_USER values ('1001','a1001','111111','王') err:java.sql.SQLSyntaxErrorException: partition table, insert must provide ColumnList
java.sql.SQLSyntaxErrorException: partition table, insert must provide ColumnList
at org.opencloudb.parser.druid.impl.DruidInsertParser.statementParse(DruidInsertParser.java:69)
at org.opencloudb.parser.druid.impl.DefaultDruidParser.parser(DefaultDruidParser.java:63)
at org.opencloudb.route.impl.DruidMycatRouteStrategy.routeNormalSqlWithAST(DruidMycatRouteStrategy.java:64)
at org.opencloudb.route.impl.AbstractRouteStrategy.route(AbstractRouteStrategy.java:76)
at org.opencloudb.route.RouteService.route(RouteService.java:129)
at org.opencloudb.server.ServerConnection.routeSQL(ServerConnection.java:201)
at org.opencloudb.server.handler.ServerLoadDataInfileHandler.parseOneLine(ServerLoadDataInfileHandler.java:391)
at org.opencloudb.server.handler.ServerLoadDataInfileHandler.end(ServerLoadDataInfileHandler.java:646)
at org.opencloudb.net.FrontendConnection.loadDataInfileEnd(FrontendConnection.java:268)
at org.opencloudb.net.FrontendConnection.rawHandle(FrontendConnection.java:426)
at org.opencloudb.net.FrontendConnection.handle(FrontendConnection.java:417)
at org.opencloudb.net.AbstractConnection.onReadData(AbstractConnection.java:313)
at org.opencloudb.net.NIOSocketWR.asynRead(NIOSocketWR.java:186)
at org.opencloudb.net.AbstractConnection.asynRead(AbstractConnection.java:274)
at org.opencloudb.net.NIOReactor$RW.run(NIOReactor.java:96)
at java.lang.Thread.run(Thread.java:745)

如果从日志看的话,可以发现是 sql 里面缺少列名。因此,只有按照 MyCAT 官方文档的步骤,一步一步来,不可逾越。

文章目录
  1. 1. 配置 MyCAT 连接 Oracle
  2. 2. Oracle 数据导出
  3. 3. 导入 MyCAT
  4. 4. 后记

欢迎来到Valleylord的博客!

本博的文章尽量原创。