经济普查全国数据库备份方案的选择研究
在经济普查数据处理过程中,为了各专业调查组能顺利地对本专业的数据进行审核汇总等操作,确保数据处理系统数据库的安全,可靠,并在意外发生时能尽快恢复到最近可用的状态,必须做好经常性数据备份。同时,为了充分利用现有的设备能力,我们需要定时进行不同服务器之间数据迁移,然后执行不同的任务,这也要通过导出/导入的办法实现。
国家和省级经济普查机构数据处理采用的数据库平台是运行在HP-UX操作系统上的Oracle9i数据库,因此,数据库的备份方案围绕0racle数据库展开讨论。
ORACLE数据库有两类备份方法。
第一类为物理备份,该方法实现数据库的完整恢复,但数据库必须运行在归档模式下(业务数据库在非归档模式下运行),且需要大容量的外部存储设备,例如磁带库;
第二类备份方式为逻辑备份,业务数据库采用此种方式,此方法不需要数据库运行在归档模式下,不但备份简单,而且可以不需要外部存储设备。
ORACLE数据库的逻辑备份分为三种模式:表备份、用户备份和完全备份。
这里不准备深入讨论三种逻辑备份的区别,有关知识请参看参考资料1。
逻辑备份及恢复是通过ORACLE提供的命令行工具EXP和IMP结合使用实现的。
参考资料1在第8章有对于EXP和IMP工具的详尽介绍和分析,我这里提到的仅仅是最常用的几个用法。
可以说由EXP导出的dmp文件的唯一用途就是用IMP进行数据的导入,但这两个命令的运行时间是很不对称的。
备份和恢复是一个整体的过程,如果是防止系统故障时数据丢失的目的,当然是希望恢复的次数越少越好,哪怕所有的备份都派不上用场。这个时候安全是第一位的。
如果是数据迁移的目的,比如从A用户到B用户,C服务器到D服务器,而且这种迁移的频率还不低,那么时间因素就值得考虑。
根据以往的经验,导入、导出无非是两种情况,一种是内部格式交换,另一种是和外部文本文件交换。
第一种:EXP导出dmp文件,IMP导入。
第二种:自己编写脚本导出文本文件,用SQLLDR工具导入。
影响导出的因素,第一是数据量,第二是复杂程度。如果要求只导出符合某些条件的数据,一般比无条件导出要费时。
影响导入的因素,第一是数据量,第二是复杂程度。如果导入的数据库有各种约束,导入时必然要增加检查的时间,如果有索引需要更新,也要额外的时间和资源。
先介绍一下我面对的数据库的情况,每个Oracle账号(用户名)下有一致的模式,分别是53个记录比较多的基本表和若干由基本表计算得出的尺寸较小的汇总表,由于汇总表的数据依赖于基本表,随之而变,所以基本表是备份的重点,
MAIN_TABLE是主表,其他表通过UUID等关键字和MAIN_TABLE及其他另外的表进行关联,带行代码的表有一个由UUID、CATE_ITEM_CODE和CYC_CODE组合而成的主键约束,
其他不带行代码的表有一个由UUID和CYC_CODE组合而成的主键约束,MAIN_TABLE除了UUID的主键约束外还具有5个索引。
一组典型的基本表记录列表如下,其中,表名中带_SUB的是带行代码的二维表。
"MAIN_TABLE" 7171639 行 772 M
"J601" 5167505 行 1.737 GB
"J602" 2003971 行 567.4M
"JB603" 275534 行 20.86M
"JB603_JB603_SUB3" 536592 行 45.57M
"JB606_JB606_SUB1" 5301882 行 342 M
"JE621_JE621_SUB4" 7664234 行 382.1M
"JE622" 52343 行 9.813M
"JG627" 1795368 行 216.8M
总计 7.3 GB
从列表中可以看出,基层表的数据量确实较大,如何快速备份及恢复(导出及导入)是需要认真研究的。
基于应用程序导入性能的要求,数据库运行在非归档模式,而且在一个阶段中(经常超过10天)不可停止数据库进行维护操作,因此物理备份不能采用。
一、Exp/imp命令组合
因为在数据整理阶段,基本表需要进行经常的改错工作,所以需要每日备份。为了能够尽快将数据恢复到某一时间点,我们采用每日执行一次特定用户的53个基本表的完全备份。
exp tom/tom file=tom.dmp log=tom.log parfile=tables.par direct=y indexes=n
从上述命令行可以看出,我们既指定了用户,又指定了表,参数文件tables.par中列出了所有需要备份的基本表表名。
direct=y参数使Oracle采用直接路径,它能有效地改善导出速度,所用时间从21分缩短到17分。对于7G字节的数据量来说,这样的导出时间已经是令人非常满意的了。
导入,我们尝试过4种命令参数
方法A1
因为原始库分别在2个用户的基本表中。
因此需要分别导入,一次导入一个用户,串行执行。
导入所用时间:20:39-10:50=9小时49分
方法A2
人工并行处理,为了利用4个CPU的能力,修改参数文件将53个基本表划分为4组,用unix的&命令移到后台并行执行。
exp I0809/I0809@118 file=I0809-p1.dmp log=I0809-p1.log parfile=tables-p1.par direct=y indexes=n &
…
exp I0809/I0809@118 file=I0809-p4.dmp log=I0809-p4.log parfile=tables-p4.par direct=y indexes=n &
如果要关闭telnet客户端,需要把上述命令保存到批处理文件a.bat ,用 nohup sh a.bat &执行。
imp I0809/I0809 file=I08091202-p1.dmp parfile=tables-p1.par ignore=y log=impI08091202-p1.log &
…
imp I0809/I0809 file=I08091202-p4.dmp parfile=tables-p4.par ignore=y log=impI08091202-p4.log &
如果要关闭telnet客户端,需要把上述命令保存到批处理文件b.bat ,用 nohup sh b.bat &执行
导入所用时间=次日10:09-16:50=17小时19分。
方法A3
根据传统的经验,如果导入数据不及时提交将会占用较大的回滚段,降低速度,所以应该用commit=y参数。可是,对于这个数据库,即使把buffer设为4096000的大值,commit=y参数也大大增加了所用时间。这里I0809用户已经建好了模式,分别是53个基本表和若干计算表,并且基本表的主键约束和索引也已创建。
imp userid=I0809/I0809 file=I080908111900.dmp parfile=tables.par log=I0809.log ignore=y commit=y buffer=4096000
所用时间=第3日01:08-20:22=28小时46分。
方法A4
为了进行比较,新建了一个用户BEE,不包含任何表。
imp userid=BEE/BEE file=I080908111900.dmp parfile=tables.par log=I0809.log ignore=y
导入所用时间=13:34 -09:58=3小时36分。
创建索引的语句我们通过下面方法得到。
首先执行表结构及约束、索引的导出,但不导出数据记录。
exp userid=I0809/I0809 file=I0809st.dmp parfile=tables.par rows=n direct=y log=expI0809st.log
然后
imp userid=BEE/BEE file=I0809st08112003.dmp parfile=tables.par log=impI0809st.log indexfile=I0809stindex.sql
我们在I0809stindex.sql可以看到I0809st08112003.dmp中包含的表结构及约束、索引。
例如:
REM CREATE TABLE "JB603" ("UUID" VARCHAR2(32) NOT NULL ENABLE,
REM "CYC_CODE" VARCHAR2(2) NOT NULL ENABLE, "B03_01" NUMBER(9, 0),
REM "B03_02" NUMBER(9, 0), "B03_03" NUMBER(9, 0), "B03_04" VARCHAR2(1),
REM "B03_INFO1" VARCHAR2(10), "B03_INFO2" VARCHAR2(10), "B03_INFO3"
REM VARCHAR2(10), "B03_INFO4" NUMBER(1, 0), "B03_INFO5" NUMBER(2, 0),
REM "B03_INFO6" NUMBER(2, 0)) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS
REM 255 STORAGE(INITIAL 65536 FREELISTS 1 FREELIST GROUPS 1) TABLESPACE
REM "EPRAS" LOGGING NOCOMPRESS ;
CREATE UNIQUE INDEX "PKJB603" ON "JB603" ("UUID" , "CYC_CODE" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 65536 FREELISTS 1
FREELIST GROUPS 1) TABLESPACE "EPRAS" LOGGING ;
然后我们可以删除一些"REM"(表示注释行),整理出一个创建约束、索引的脚本makeindex.sql
CREATE UNIQUE INDEX "PKJB603" ON "JB603" ("UUID" , "CYC_CODE" )
PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 65536 FREELISTS 1
FREELIST GROUPS 1) TABLESPACE "EPRAS" LOGGING ;
ALTER TABLE "JB603" ADD CONSTRAINT "PKJB603" PRIMARY KEY
("UUID", "CYC_CODE") USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
STORAGE(INITIAL 65536 FREELISTS 1 FREELIST GROUPS 1) TABLESPACE
"EPRAS" LOGGING ENABLE ;
而创建表语句,如CREATE TABLE "JB603"是不必要的,因为导入dmp时会自动创建,如果写了,oracle将抛出一个错误信息,但不影响其余语句的执行,因此保留也无妨。
@makeindex.sql
重建所有基本表索引总共用时=02:28:06-02:21:24=6分42。
方法A5
exp导出不包含约束和索引的数据
exp bee/bee file=beec.dmp log=bee.log parfile=tables.par direct=y indexes=n CONSTRAINTS=n
导出所用时间=19:17-19:02=17分
在一个用应用程序产生的用户下删除tables.par中包含的53张表
imp userid=bee4/bee4 file=beec.dmp parfile=tables.par log=beecimp.log ignore=y
date
导入所用时间=14:53-13:15=1小时38分
@makeindex.sql
date
重建所有基本表索引和约束总共用时=11:05:51-10:28:26=37:25(索引建好以后,改为主键约束基本不用时间)
这样,我们通过采用Direct=y参数加快了导出,通过在导出时不导出约束和索引,并删除目标表导入,然后重建索引的办法加快了导入。
在Oracle 10g中新增加了expdp和impdp命令,这二个命令用到了新的数据泵(Pump)技术,支持并行操作,特别是导入速度有极大的提高。
方法B1
expdp bee/bee dumpfile=bee-%U.dmp directory=dmpdir logfile=bee..log parallel=5 parfile=tables.par EXCLUDE=INDEX,CONSTRAINT,TABLE_STATISTICS
06:49:40 - 06:32:33 =17分07 虽然expdp不再有direct=y参数,但它默认自动采用直接路径,当无法采用直接路径时改用常规路径。尽管有一些时间用于分析,仍然与exp的速度相当。
impdp bee2/bee2 dumpfile=bee-%U.dmp directory=dmpdir logfile=beei.log parallel=5 parfile=tables.par TABLE_EXISTS_ACTION=REPLACE REMAP_SCHEMA=bee:bee2
导入所用时间=07:05:11 - 06:49:40=15分31与exp的速度相当,和imp的速度不是一个数量级的,当然这里没有导入约束和索引,但即使导入约束,仍然比imp快了许多,参看下面方法B2。
导入后建立约束和索引的时间,08:55:42-08:24:38 =31分04
总时间=17:07+15:31+31:04=63分38
方法B2
导出时包含索引和约束,但目标表不存在
expdp bee/bee dumpfile=beei-%U.dmp directory=dmpdir logfile=bee.log parallel=5 parfile=tables.par
17:04-16:48-09:36=0:16
impdp bee3/bee3 dumpfile=beei-%U.dmp directory=dmpdir logfile=beeimp.log parallel=5 parfile=tables.par TABLE_EXISTS_ACTION=REPLACE REMAP_SCHEMA=bee:bee3
导入所用时间=17:57:13-17:04:36=52分37
总时间=16+52:37=69分
方法B1和B2两者速度持平,但方法B2步骤简单了许多,更适合一般用户使用。
Exp/imp命令组合小结:
有条件的话,首选Oracle 10g平台,采用expdp导出包含约束和索引的数据,然后impdp命令导入,一气呵成。虽然只能将dmp文件存在服务器端,但考虑到即使用exp也是telnet到服务器后执行的(为了充分利用服务器的高速I/O和大容量存储,并减少网络流量),两者没有本质的区别。而且expdp/impdp还具有很多可管理的特性,可以在导出/导入过程中挂起、恢复、终止任务。
另一处要注意的是dmpdir目录需要预先建立,
conn / as sysdba
create directory dmpdir as '/oradata/dmp';
grant read,write ON DIRECTORY dmpdir to tom;
而且给导出和导入用户都授权读写,可以用一个脚本完成,不费事。
GRANT READ ON DIRECTORY "SYS"."DMPDIR" TO "BEE3";
GRANT WRITE ON DIRECTORY "SYS"."DMPDIR" TO "BEE3";
GRANT "CONNECT" TO "BEE3";
GRANT "DBA" TO "BEE3";
GRANT "RESOURCE" TO "BEE3";
如果要在经济普查数据库这样的Oracle 9i平台导出和导入,采用exp导出不包含约束和索引的数据,再删除目标用户基本表的约束和索引(如果存在),然后imp命令导入,再用脚本建立约束和索引,commit=y参数不必使用。
上述各方法的参数以及运行时间的比较见附表:
(方法A:Oracle9i exp+imp,方法B:10g expdp+impdp)
方法 |
编号 |
提交 |
并行 |
导出约束 |
导出索引 |
导入前删目标表索引和约束 |
导出时间 |
导入时间 |
建立索引时间 |
总时间 |
首选 |
A |
1 |
N |
N |
N |
N |
N |
17分 |
9小时49分 |
0 |
10小时06分 |
否 |
A |
2 |
N |
Y |
N |
N |
N |
17分 |
17小时19分 |
0 |
17小时36分 |
否 |
A |
3 |
Y |
N |
N |
N |
N |
17分 |
28小时46分 |
0 |
29小时03分 |
否 |
A |
4 |
N |
N |
Y |
N |
Y |
17分 |
3小时36分 |
7分 |
4小时 |
否 |
A |
5 |
N |
N |
N |
N |
Y |
18分 |
1小时38分 |
38分 |
2小时34分 |
是 |
B |
1 |
N |
Y |
N |
N |
Y |
17分 |
16分 |
31分 |
1小时04分 |
是 |
B |
2 |
N |
Y |
Y |
Y |
Y |
16分 |
53分 |
0 |
1小时09分 |
是 |
从上表可以看出:
1 手工模拟并行方法效果不佳。
2 一个教训:约束往往和一个唯一索引/主键相关联,所以仅仅利用indexes=n 选项是不够的,必须添加CONSTRAINTS=n,才能真正避免导入时的检查。
这里我们之所以能采用Drop指定表(表上的约束和索引自动被删除),然后导入数据,然后建立约束和索引的方法,原因有下面几方面。
1 数据来源比较干净,原来的数据库有严格的约束,垃圾数据不存在。
2 目标数据库没有记录,不会发生新旧数据的冲突。
3 数据库结构比较简单,没有外键、触发器等,导入时不必严格限定先后次序,也不用索引查找键值。
参考资料1中提到exp,sqlldr都有direct=y选项,绕过sql引擎,直接读写数据块。而imp没有此选项,只能用生成INSERT语句的办法,这一点可以通过导入进行时,用oem企业管理器观察系统用户进程得到。
二、文本导出和SQLLDR命令装载
以上讨论的是EXP和IMP结合使用的数据备份方法,下面讨论输出文本文件和SQLLDR装载的方法。
文本文件是各种数据库管理系统都支持的外部数据输入格式,SQLLDR是Oracle提供的高效的文本装入工具。SQLLDR利用一个称为控制文件的文本文件中的描述确定外部文本和数据库表字段的格式对应关系,它支持并行处理和直接路径装载。
一个典型的控制文件load.ctl如下所示
LOAD DATA
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(DEPTNO,
DNAME,
LOC
)
它对应的文本文件data.txt
10,Sales,"""USA"""
20,Accounting,"Virginia,USA"
30,Consulting,Virginia
40,Finance,Virginia
50,"Finance","",Virginia
60,"Finance",,Virginia
通过命令行sqlldr userid=tkyte/tkyte control=load.ctl data=data.txt即可将data.txt中的记录装载到DEPT表。
SQLLDR提供了两种方法装载数据——常规路径装载与直接路径装载。常规路径装载是SQLLDR的默认装载方法。为了能够进行直接路径装载,在激活SQLLDR时,必须将DIRECT = TRUE添加到命令行参数中。常规路径装载具有直接路径装载不需要的一些额外步骤。这些额外步骤增加了系统处理的额外开销,使得常规路径装载的速度慢于直接路径装载。格式化SQL INSERT语句以及搜索SGA内存高速缓冲区的额外步骤与其他同时并发运行在数据库上的进程产生竞争。虽然出于速度原因,我们倾向于使用直接路径装载,但是在一些限制情形下,还是应使用常规路径装载。
下面是一些情形与案例,要求最好或必须使用常规路径装载方法,而不能使用直接路径装载:
■ 如果被装载的表是被索引的并且被并发访问的,或者如果要对表进行插入或删除,必须使用常规路径装载。
■ 当在控制文件中使用SQL函数时,必须使用常规路径装载。当使用直接路径装载时,SQL函数将不适用。
■ 当装载的表是一个簇表时。
■ 当装载少量记录到一个大型索引表,或当表具有引用完整性或检查约束时。这种情形下使用常规路径装载要好一些。
装载少量的记录到一个带有索引和/或引用及检查约束的大型表不是使用直接路径装载的一个限制因素,但是使用常规路径装载更为有效。在存在索引的情况下,或许常规路径装载在装载数据时更新索引要快一些,而且不需要进行一个大的排序/组合来创建新的索引。对于引用和检查约束,直接路径装载要求在装载前禁止这些约束。在全部数据装载后,重新允许检查约束,整个数据表都将根据这些约束进行检查,而不仅仅是被装载的数据。
虽然多种文档中提到,SQLLDR是Oracle最快的装载数据办法,但是对于本例这个特殊的数据库,它不是最有效的,理由有下面几点:
1 基本表的数量众多,书写控制文件工作繁重;
2 基本表的数量众多,书写导出文本文件脚本工作繁重;脚本通常采用spool命令和select语句配合的办法,利用sqlplus -silent 用户 @脚本名的办法可最大限度地减少由于在终端回显的时间。(参考资料1第9章提供了一个PL/SQL过程unloader实现从一个SQL查询语句自动产生导出的文本文件和相应的控制文件。参考资料3 提供了利用OCI Array Fetch实现的UNLOAD程序,执行速度比单行Fetch的PL/SQL过程快几倍)
3 需要产生大量的文件,操作比较繁琐,一般用户不易掌握;
4 如果能使用Oracle 10g版本,expdp+impdp的效率完全可以与SQLLDR并行处理相当。
5 导出文本文件的时间远长于exp命令的时间。(exp J601 表用时3分钟,unloader J601表20分钟也没有完成,spool脚本的运行时间和unloader相比,约为后者的3/4)
如果需要从其他数据库导入数据,文本文件和SQLLDR装载是较好的一种办法,事实上,经济普查数据处理程序采用了这种方法实现从Windows版MySQL数据库向Unix的Oracle导入数据。
三、结论
从上述两种方法的测试中,我们可以看出。大量数据迁移时,将数据先迁移再重建索引的原则都是不变的。如果明显是从一个符合约束的来源表导出数据到一个空表,那么约束也可以延迟建立。直接路径是Oracle特有的可提高导入/导出效率的选项,它的应用受到了某些限制,比如exp的query参数和direct参数便不兼容。
参考资料 1清华大学出版社《Oracle专家高级编程》(〈Expert one-on-one Oracle 〉Thomas Kyte著)
2机械工业出版社《Oracle 8_8i 开发使用手册》
3 OCI实现的UNLOAD程序楼方鑫, http://www.anysql.net