近期打算写一个【从ORACLE/MySQL到OceanBase】序列。本文是第四篇。前期直播回放请到:https://cs.enmotech.com/course/play/17 。

概述

表的数据导出与导入,是开发常提的需求,开发也可以自己独立完成。场景有表备份、线上和线下之间数据传输、不同数据库类型间数据传输。

今天就介绍一下ORACLE、MySQL和OceanBase怎么满足这个需求,然后引申开介绍大批量数据导出和导入实践经验。

ORACLE数据导出&导入


ORACLE导出表数据时量少的时候,可以用sqlplusspool语句,量大的时候需要用专用工具。

EXP/IMP

数据导出属于逻辑备份的一种,ORACLE常用表数据导出和导入命令有exp/impexpdp/impdp。这种备份不支持增量备份。

exp/imp命令功能很丰富,支持按全库导、按用户(schema)、按表导出/导入。

​exp hr/hr GRANTS=Y TABLES="(emp,dept)" FILE=/home/oracle/exp/hr.dmp LOG=/home/oracle/exp/log/hr.log​

这个导出文件只有ORACLE自己能用。导入的时候只能使用imp命令,并且要事先确认导入的对象不存在。

sqlldr


ORACLE还提供了一个数据导入工具sqlldr,用于导入csv文件里的数据。sqlldr支持Linux和Windows。

sqlldr导入需要输入两个文件。一个是控制文件,用于指定数据文件的格式、导入的方法等。控制文件格式有点麻烦。

数据文件就是特定分隔符的csv文件,可以指定多个。

sqluldr2


sqluldr2是原支付宝DBA黄忠开发的ORACLE数据快速导出工具,开发语言是C语言,基于OCI,有32位和64位程序,支持Linux和Windows平台。这个工具特点就是:够快!支持按表或者SQL导出文件,能将上亿的数据导出为文件,能用于生产环境。对于文件的内容可以是纯文本、CSV文件、INSERT SQL(适配ORACLE/MySQL)或者搜索用的特定格式。大表导出文件支持文件分割。

[oracle@rac1 ~]$ ./sqluldr2_linux64_10204.bin

SQL*UnLoader: Fast Oracle Text Unloader (GZIP, Parallel), Release 4.0.1
(@) Copyright Lou Fangxin (AnySQL.net) 2004 - 2010, all rights reserved.

License: Free for non-commercial useage, else 100 USD per server.

Usage: SQLULDR2 keyword=value [,keyword=value,...]

Valid Keywords:
user = username/password@tnsname
sql = SQL file name
query = select statement
field = separator string between fields
record = separator string between records
rows = print progress for every given rows (default, 1000000)
file = output file name(default: uldrdata.txt)
log = log file name, prefix with + to append mode
fast = auto tuning the session level parameters(YES)
text = output type (MYSQL, CSV, MYSQLINS, ORACLEINS, FORM, SEARCH).
charset = character set name of the target database.
ncharset= national character set name of the target database.
parfile = read command option from parameter file

for field and record, you can use '0x' to specify hex character code,
\r=0x0d \n=0x0a |=0x7c ,=0x2c, \t=0x09, :=0x3a, #=0x23, "=0x22 '=0x27

[oracle@rac1 ~]$ ./sqluldr2_linux64_10204.bin user=TUSER/******@11.***.***.219:1521/rac query=BIGTABLE file=BIGTABLE_%B.txt batch=yes rows=1000000
0 rows exported at 2019-07-11 14:06:22, size 0 MB.
1000000 rows exported at 2019-07-11 14:06:35, size 392 MB.
output file BIGTABLE_1.txt closed at 1000000 rows, size 393 MB.
1000000 rows exported at 2019-07-11 14:06:47, size 789 MB.
output file BIGTABLE_2.txt closed at 1000000 rows, size 789 MB.
1000000 rows exported at 2019-07-11 14:07:00, size 1181 MB.
output file BIGTABLE_3.txt closed at 1000000 rows, size 1185 MB.
1000000 rows exported at 2019-07-11 14:07:13, size 1581 MB.
output file BIGTABLE_4.txt closed at 1000000 rows, size 1581 MB.
1000000 rows exported at 2019-07-11 14:07:29, size 1977 MB.
output file BIGTABLE_5.txt closed at 1000000 rows, size 1977 MB.
1000000 rows exported at 2019-07-11 14:07:46, size 2373 MB.
output file BIGTABLE_6.txt closed at 1000000 rows, size 2373 MB.
1000000 rows exported at 2019-07-11 14:08:02, size 2765 MB.
output file BIGTABLE_7.txt closed at 1000000 rows, size 2768 MB.
1000000 rows exported at 2019-07-11 14:08:17, size 3164 MB.
output file BIGTABLE_8.txt closed at 1000000 rows, size 3165 MB.
1000000 rows exported at 2019-07-11 14:08:30, size 3556 MB.
output file BIGTABLE_9.txt closed at 1000000 rows, size 3560 MB.
1000000 rows exported at 2019-07-11 14:08:44, size 3952 MB.
output file BIGTABLE_10.txt closed at 1000000 rows, size 3956 MB.
1000000 rows exported at 2019-07-11 14:08:57, size 4352 MB.
output file BIGTABLE_11.txt closed at 1000000 rows, size 4355 MB.
1000000 rows exported at 2019-07-11 14:09:11, size 4751 MB.
output file BIGTABLE_12.txt closed at 1000000 rows, size 4754 MB.
1000000 rows exported at 2019-07-11 14:09:24, size 5150 MB.
output file BIGTABLE_13.txt closed at 1000000 rows, size 5153 MB.
310000 rows exported at 2019-07-11 14:09:29, size 5276 MB.
output file BIGTABLE_14.txt closed at 310000 rows, size 5276 MB.

sqluldr2在导出数据的同时还会生成sqlldr导入用的控制文件。通常可以直接使用。


MySQL数据导出&导入

MySQL支持在查询的时候直接将结果输出到文件,可以指定分隔符。语法是:


SELECT ... FROM TABLE_A

SELECT ... FROM TABLE_A

INTO OUTFILE "/path/to/file"
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n';

MySQL 也支持在命令行下将导出的文件再导入MySQL文件。

LOAD DATA INFILE "/path/to/file" INTO TABLE table_name;

注意:如果导出时用到了FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n'语句,那么LOAD时也要加上同样的分隔限制语句。

不过上面方法适合数据量不大的情况。数据量大时或者表很多时,还是要用专用工具。

mysqldump

mysqldump是专门用于导出MySQL数据,功能非常丰富,支持导出全库、指定库、指定表、指定条件的数据导出。也可以只导出表结构不导数据,或者只导数据不导表结构。导出的文件是sql文件,内容是insert sql

有点特别的是这个insert sql里会有多个values子句。

MySQL导入时的性能可能取决于实例的性能。如果开启参数sync_binlog=0innodb_flush_log_at_trx_commit=0,写入性能会更好。当然风险就是如果实例或者主机宕机,可能引起数据丢失或者主从不一致。

OceanBase数据导出&导入


OceanBase集群支持多租户(实例),租户的兼容模式有兼容MySQL和ORACLE两种。这里主要说MySQL租户的数据导出和导入。

mysqldump

OceanBase的MySQL租户兼容MySQL连接协议和常用语法。所以mysqldump工具可以用来导出MySQL租户数据。不过稍注意的是如果是全库导出,由于OceanBase还不支持trigger特性,因此查看trigger定义的SQL会报错,需要通过参数--skip-triggers避免。

在导出大表时会碰到一个超时问题,代码-4012这个是OceanBase里最常见的报错之一。

$mysqldump -h11.***.84.84 -uroot@tnt_mysql#obdemo -P2883 -p'rootpwd' sysbenchtest sbtest1 > sbtest1.sql
mysqldump: Error 4012: Timeout when dumping table `sbtest1` at row: 3516280

OceanBase的每个SQL执行时间默认不能超过租户变量ob_query_timeout的设置。默认值是10000000,单位微秒。对于慢SQL而言,这个参数太低,需要调整大一些。mysqldump不支持设置这个,所以需要对业务租户设置这个变量,会对所有会话生效。

set global ob_query_timeout=1000000000;
show global variables like '%ob_query_timeout%';

改了这个之后,还可能碰到一个事务超时错误。

$mysqldump -h11.***.84.84 -utestuser@tnt_mysql#obdemo -P2883 -p'testpwd' sysbenchtest sbtest1 > sbtest1.sql
date
mysqldump: Error 4012: Transaction is timeout when dumping table `sbtest1` at row: 100000000

需要调大一下事务超时时间。

set global ob_trx_timeout=1000000000;
set global ob_trx_idle_timeout=1200000000;
show global variables where variable_name in ('ob_trx_timeout','ob_trx_idle_timeout');

$time mysqldump -h11.***.84.84 -utestuser@tnt_mysql#obdemo -P2883 -p'testpwd' --skip-opt --skip-triggers sysbenchtest sbtest1 > sbtest1.sql

real 11m35.295s
user 4m28.111s
sys 1m22.040s

有关OceanBase的timeout问题请参考前文《从ORACLE/MySQL到OceanBase:数据库超时机制》。

DataX


DataX是阿里巴巴开源的数据交换平台,支持同构和异构数据源之间的数据传输。支持的数据源有:ORACLE、MySQL、DB2、SQLServer、OceanBase、文本文件等。

DataX是用Java开发的,框架设计很好,对二次开发非常友好。有关DataX的介绍,网上资料非常多。这里主要介绍用DataX做OceanBase的数据导出和导入。在外部客户场景中,有时候DataX做ORACLE或DB2到OceanBase的数据离线传输,或者OceanBase到大数据平台的数据同步。

DataX在安装后可以直接使用,需要针对要做导出或导入的数据写一个json格式的配置文件。

导出

下面是一个导出的json配置文件示例,需要指定reader(描述源端数据信息)和writer(描述目标端信息)。readerwriter支持很多种类型。具体可以查看开源代码目录:https://github.com/alibaba/datax/

{
"job":{
"setting":{
"speed":{
"channel":10
},
"errorLimit":{
"record":0, "percentage": 0.02
}
},
"content":[
{
"reader":{
"name":"oceanbasev10reader",
"parameter":{
"where":"",
"timeout":10000,
"readBatchSize":100000,
"readByPartition":"true",
"column":[
"id","k","c","pad"
],
"connection":[
{
"jdbcUrl":["||_dsc_ob10_dsc_||obdemo:tnt_mysql||_dsc_ob10_dsc_||jdbc:mysql://11.***.84.83:2883/sysbenchtest"],
"table":["sbtest1"]
}
],
"username":"testuser",
"password":"testpwd"
}
},
"writer":{
"name":"txtfilewriter",
"parameter":{
"path":"/home/t4/ob/datax3",
"fileName":"sbtest1",
"writeMode":"truncate",
"dateFormat":"yyyy-MM-dd",
"charset":"UTF-8",
"nullFormat":"",
"fileDelimiter":"||"
}
}
}
]
}
}

这个配置文件中表​​sbtest1​​是个分区表,结构如下:

CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.0' REPLICA_NUM = 3 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 10 TABLEGROUP = 'tgsbtest'
partition by hash(id) partitions 5
;

我使用一定的技术将这5个分区的主副本打散到1-1-1的每个节点上。具体原理可以参考前文《揭秘OceanBase的弹性伸缩和负载均衡原理》。DataX在导出分区表时,可以选择按分区并行导出。从下面的日志里一段也可以看到这一点。

运行命令就是 :​python ../bin/datax.py sbtest1.json​

运行过程中日志如下:

$python ../bin/datax.py sbtest1.json
DataX (********), From Alibaba !
Copyright (C) 2010-2017, Alibaba Group. All Rights Reserved.

<......>

2019-07-11 20:11:43.520 [0-0-0-reader] INFO ReaderTask - exe sql: select /*+READ_CONSISTENCY(WEAK)*/ id,k,c,pad from sbtest1 partition(p0) where (id) > (?) order by id asc limit 100000
Thu Jul 11 20:11:43 GMT+08:00 2019 INFO: success to create connection, ip=11.***.84.83, port=2883, database=sysbenchtest, url=jdbc:oceanbase://11.***.84.83:2883/sysbenchtest?yearIsDateType=false&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&rewriteBatchedStatements=true&socketTimeout=1800000&connectTimeout=60000
2019-07-11 20:11:43.525 [0-0-0-reader] WARN ObReaderUtils - setAutoCommit=true;set ob_query_timeout=172800000000;set ob_trx_timeout=172805000000;
2019-07-11 20:11:43.525 [0-0-0-reader] WARN ReaderTask - sbtest1 save point : 21000000
2019-07-11 20:11:43.617 [0-0-3-reader] INFO ReaderTask - end of sql: select /*+READ_CONSISTENCY(WEAK)*/ id,k,c,pad from sbtest1 partition(p3) where (id) > (?) order by id asc limit 100000, 100000rows are read.

<......>

从这段日志可以看出DataX是根据主键分段取数据。即使主键是联合主键(a,b,c),也支持。条件将会是 (a,b,c)>(?,?,?) order by a,b,c limit 100000`OceanBase支持向量表达式。

下面是运行结果

2019-07-11 20:24:48.689 [job-0] INFO  StandAloneJobContainerCommunicator - Total 100000000 records, 19388888898 bytes | Speed 18.62MB/s, 100704 records/s | Error 0 records, 0 bytes |  All Task WaitWriterTime 186.551s |  All Task WaitReaderTime 4,525.439s | Percentage 100.00%
2019-07-11 20:24:48.689 [job-0] INFO LogReportUtil - report datax log is turn off
2019-07-11 20:24:48.690 [job-0] INFO JobContainer -
任务启动时刻 : 2019-07-11 20:08:11
任务结束时刻 : 2019-07-11 20:24:48
任务总计耗时 : 997s
任务平均流量 : 18.62MB/s
记录写入速度 : 100704rec/s
读出记录总数 : 100000000
读写失败总数 : 0

$ls -lrth /home/t4/ob/datax3/
total 19G
-rw-rw-r-- 1 admin admin 3.7G Jul 11 20:24 sbtest1__6e7ffc40_d10f_493e_99d5_2c0a9f6f3391
-rw-rw-r-- 1 admin admin 3.7G Jul 11 20:24 sbtest1__77a7f46d_4098_4eb7_b62e_46f1e1aeb9df
-rw-rw-r-- 1 admin admin 3.7G Jul 11 20:24 sbtest1__b01e988f_e7aa_479c_a100_0bb6032809f1
-rw-rw-r-- 1 admin admin 3.7G Jul 11 20:24 sbtest1__1327d76a_369f_4b2b_b12b_1e04d4e67976
-rw-rw-r-- 1 admin admin 3.7G Jul 11 20:24 sbtest1__bb79704a_0331_4b48_ba12_0b6d9181ec17


从日志上看1亿笔数据导出文件用了997s,跟mysqldump相差不大。但是导出为多个csv文件可以方便快速导入到其他数据源。

分割文件

前面表sbtest1按分区导出了5个文件,后面可以直接将这5个文件用DataX导入到OB。不过这样可能性能不是最好。根据实践最快的做法是将这5个文件再切分为更多文件,然后起多个进程分别导入,这些进程可以在一个节点或者多个节点。

DataXtxtfilewriter好像暂不支持分割文件,所以需要使用Linux的split命令将前面导出的文件按行数或大小分割为多个文件,然后移动到不同的工作目录。

$TFILES=`ls sbtest1_*`

$for fp in $TFILES;do echo $fp; split -l 1000000 $fp -d -a 4 `echo $fp`_S_ ; done
/home/t4/ob/datax3/sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5
/home/t4/ob/datax3/sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4
/home/t4/ob/datax3/sbtest1__58728259_35af_426f_ae9c_d5e660ae747a
/home/t4/ob/datax3/sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816
/home/t4/ob/datax3/sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46

分割后的文件后缀是 [0000-0019],前缀是相应的文件名。按照后缀将这些文件分到多个节点的目录里。这里平分为4份。每份里有25个文件,分别包含5个分区的部分数据。

Alibaba的DataX目前并没有开源其分布式执行能力,所以还需要复制不同文件到不同节点目录上并分别启动导入进程。

导入

不管是用什么导入技术,导入的性能调优思路都是一样的,尽可能的并发写入。对于数据库能做的,如果是分区表的话,并发写入的时候很可能能分别写入不同的分区。这时候每次写入的事务特点就很重要。

如果每个事务写入的数据不跨越分区,对于OceanBase而言就是单机事务,这样并发写入不同分区的做法性能是最优的。如果每个事务写入的数据会跨越分区,很可能就是分布式事务,则这种并发写入的性能反而不好。理解了这一点,就理解了在OceanBase上做批处理时如何优化SQL。

可以看看其中一个节点要处理的文件

$ls -lh
total 4.7G
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5_S_0000
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5_S_0001
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5_S_0002
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5_S_0003
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5_S_0004
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4_S_0000
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4_S_0001
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4_S_0002
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4_S_0003
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4_S_0004
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 sbtest1__58728259_35af_426f_ae9c_d5e660ae747a_S_0000
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 sbtest1__58728259_35af_426f_ae9c_d5e660ae747a_S_0001
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__58728259_35af_426f_ae9c_d5e660ae747a_S_0002
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__58728259_35af_426f_ae9c_d5e660ae747a_S_0003
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__58728259_35af_426f_ae9c_d5e660ae747a_S_0004
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816_S_0000
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816_S_0001
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816_S_0002
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816_S_0003
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816_S_0004
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46_S_0000
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46_S_0001
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46_S_0002
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46_S_0003
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46_S_0004

按文件名排序的时候,连续的文件是来自于同一个分区。如果DataX并发顺序读取这些文件并写入到OceanBase时,很可能会在每个时间点并发写入的是同一个分区。这样上面的并发写入不同分区的效果就打折扣了。所以这里需要一个小技巧,将每个文件名后面的[0000-0019]换到文件名的前部。

$ls |awk -F'_S_' '{print "mv "$0" "$2"_S_"$1}' |bash
$ls -lh
total 4.7G
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 0000_S_sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 0000_S_sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 0000_S_sbtest1__58728259_35af_426f_ae9c_d5e660ae747a
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 0000_S_sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 0000_S_sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 0001_S_sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 0001_S_sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 0001_S_sbtest1__58728259_35af_426f_ae9c_d5e660ae747a
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 0001_S_sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816
-rw-rw-r-- 1 admin admin 191M Jul 12 22:19 0001_S_sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0002_S_sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0002_S_sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0002_S_sbtest1__58728259_35af_426f_ae9c_d5e660ae747a
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0002_S_sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0002_S_sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0003_S_sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0003_S_sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0003_S_sbtest1__58728259_35af_426f_ae9c_d5e660ae747a
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0003_S_sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0003_S_sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0004_S_sbtest1__08eef62b_fa62_4749_9c39_16baa6a6efc5
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0004_S_sbtest1__502d861c_60c7_48a3_b183_34c64c8b46b4
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0004_S_sbtest1__58728259_35af_426f_ae9c_d5e660ae747a
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0004_S_sbtest1__7b6396ae_012f_4ee2_8413_5f05ad6dc816
-rw-rw-r-- 1 admin admin 192M Jul 12 22:19 0004_S_sbtest1__f6f34d67_03e6_4571_89c6_7b15172a9c46

这样顺序读取的文件是来自于不同分区的数据。

跟导出一样,导入也需要一个配置文件。首先建导入表sbtest1_bak

MySQL [sysbenchtest]> create table sbtest1_bak like sbtest1;
Query OK, 0 rows affected (0.29 sec)

其次准备配置文件

$cat sbtest1_bak_part1.json
{
"job":{
"setting":{
"speed":{
"channel":32
},
"errorLimit":{
"record":0, "percentage": 0.02
}
},
"content":[
{
"reader":{
"name":"txtfilereader",
"parameter":{
"path":["/data/datax3/0000"],
"encoding":"UTF-8",
"column":[
{ "index":0, "type":"long" }
,{ "index":1, "type":"long" }
,{ "index":2, "type":"string" }
,{ "index":3, "type":"string" }
],
"fieldDelimiter":"||",
"fileFormat":"text"
}
},
"writer":{
"name":"oceanbasev10writer",
"parameter":{
"writeMode":"insert",
"column":[
"id","k","c","pad"
],
"connection":[
{
"jdbcUrl":"||_dsc_ob10_dsc_||obdemo:tnt_mysql||_dsc_ob10_dsc_||jdbc:mysql://11.***.84.83:2883/sysbenchtest",
"table":["sbtest1_bak"]
}
],
"username":"testuser",
"password":"testpwd",
"batchSize":256,
"activeMemPercent":"90"
}
}
}
]
}
}

运行导入命令:

nohup python ../bin/datax.py sbtest1_bak_part1.json > /tmp/datax_csv2ob.log &
nohup python ../bin/datax.py sbtest1_bak_part2.json > /tmp/datax_csv2ob.log &
nohup python ../bin/datax.py sbtest1_bak_part3.json > /tmp/datax_csv2ob.log &
nohup python ../bin/datax.py sbtest1_bak_part4.json > /tmp/datax_csv2ob.log &

最终导入结束时,DataX会给一个报告:

2019-07-13 11:51:07.184 [job-0] INFO  LogReportUtil - report datax log is turn off
2019-07-13 11:51:07.184 [job-0] INFO JobContainer -
任务启动时刻 : 2019-07-13 11:33:23
任务结束时刻 : 2019-07-13 11:51:07
任务总计耗时 : 1064s
任务平均流量 : 4.35MB/s
记录写入速度 : 23518rec/s
读出记录总数 : 25000000
读写失败总数 : 0

每个进程处理2500万数据,用了约18分钟,4个进程都差不多。所以总的1亿数据用4个节点导入,用了18分钟。由于我的测试环境OB的机器能力一般,所以这个结果并不算快。

批处理性能监控

监控OceanBase租户写入情况,可以看到租户的三节点都有写入,因为sbtest1_bak也是分区表,分区分布在所有节点上。

从ORACLE/MySQL到OceanBase:数据导出&导入_sql



从ORACLE/MySQL到OceanBase:数据导出&导入_sql_02


跑批量处理业务时需要监控租户的增量内存使用情况。如上图,Active就反映当前内存的消耗速度,PCT.列基本就是增量内存的使用比例。当这个比例超过Minor freeze的阈值时会触发转储,超过限流的比例时会触发限流(即限速)。多种手段就是为了保证增量内存不会被快速用尽。如果增量内存用尽,业务会碰到报错:over tenant memory limit,即业务写不进去了。这种情况比较类似ORACLE归档空间满了,Redo不能切换,业务也写不进去。

如果各个节点写入压力不均衡的时候,还需要详细看各个节点的内存写入情况。如下。mem_used_pct是各个节点增量内存的利用率。同样转储和限流在每个节点会单独触发。freeze_cnt是当前已转储的次数。

select tenant_id, ip, port, round(active/1024/1024/1024) active_gb, round(total/1024/1024/1024) total_gb, round(freeze_trigger/1024/1024/1024) freeze_trg_gb, 
round(mem_limit/1024/1024/1024) mem_limit_gb, freeze_cnt, round(total/mem_limit, 3) mem_used_pct
from oceanbase.`gv$memstore` where tenant_id>1000
order by tenant_id;

+-----------+--------------+------+-----------+----------+---------------+--------------+------------+--------------+
| tenant_id | ip | port | active_gb | total_gb | freeze_trg_gb | mem_limit_gb | freeze_cnt | mem_used_pct |
+-----------+--------------+------+-----------+----------+---------------+--------------+------------+--------------+
| 1001 | 11.***.84.84 | 2882 | 10 | 10 | 12 | 30 | 1 | 0.328 |
| 1001 | 11.***.84.79 | 2882 | 10 | 10 | 12 | 30 | 1 | 0.331 |
| 1001 | 11.***.84.78 | 2882 | 10 | 10 | 12 | 30 | 1 | 0.330 |
+-----------+--------------+------+-----------+----------+---------------+--------------+------------+--------------+
3 rows in set (0.03 sec)

批处理业务


DataX是同构/异构数据源数据交换平台,框架非常适合业务定制开发,可以跟业务结合使用。其最关键的特点是具备水平扩展能力。也就是说如果业务觉得导出和导入速度还是太慢,则可以加节点处理。

有些企业有自己的研发团队,也有类似的数据同步框架。在做大批量的数据导出/导入时,如果要发挥最大性能,基本上都是遵循数据库里数据分布特点,在客户端尽可能的运行更多的并发去同时处理数据。

不同关系数据库在大批量写的情况下瓶颈并不完全相同。OceanBase的架构决定了对写非常友好,增量数据在内存中,所以内存很可能会成为瓶颈。好在现在内存可以很大,512G内存也很便宜,加上OceanBase的分布式架构有很好的水平扩展能力,内存瓶颈总是能解决的。

不过OceanBase的架构最早是为OLTP交易设计的,适合处理短小快的事务。对于批处理业务,我们建议提高并发,但是不建议每个事务很大。因为OceanBase的架构有着很好的弹性伸缩和高可用切换能力。当内部分区角色切换的时候,要把大事务的状态(未提交的数据)也切换过去,这个成本很高。当并发很高的时候OceanBase会选择等待100ms,如果事务还不提交则强制回滚该事务。

那种大事务批量处理的方法适合在集中式数据库架构下。对业务来说写起来可能很简单,但是数据库却要承受很大的风险。比如说ORACLE上的大事务,会消耗很多UNDO,如果同时有很多大查询,业务查询还可能会碰到ORA-01555错误。当然解决办法也有,就是加大UNDO表空间大小

批处理业务虽然要处理很多数据,并不一定要在几个大事务内处理完。在设计上可以将任务分解为很多小的事务,然后用一定的并发去处理这些事务。类似于Hadoop的处理思路。在OceanBase下,如果并发很高,事务大小建议在1万笔更新左右。这个值只是为了方便有些人追求确定的标准。实际还要看内存大小、事务修改的数据行大小。任何经验标准都要跟实际情况结合。

总结


本文以DataX为例简单演示了OceanBase上大批量数据导出和导入的实践经验,相关OceanBase的原理可以查看之前的文章。对于有批处理经验的开发人员来说,这个经验很好理解,只需要再了解一下OceanBase的特点即可。

数据导出和导入适用于特殊的场景,不太适合实时同步。DataX也可以配置增量同步,原理是根据where条件。OceanBase自身也在研发类似MySQL的load data功能。此外OceanBase配套产品还有备份与恢复,适合集群和租户级别的备份恢复需求。

对于围绕OceanBase的实时同步需求,通过产品OMS来解决。OMS支持ORACLE/MySQL和OceanBase之间的实时同步。对于DB2的支持目前也在研发中。OMS以后有机会再介绍。