传输表空间技术是数据迁移中非常重要的技术之一。它的作用是将一组自包含(Self-Contained),只读的表空间只导出元数据,然后在操作系统层面将表空间的数据文件直接复制到目标平台,再将元数据导入数据字典,即完迁移。  可以用来快速迁移表空间,也可以用来迁移整个库。

        Oracle用exp/imp或expdp/impdp来支持这个技术。

整理了三种情况:

一。 非跨平台的正常迁移流程。

二。不同字节序跨平台的表空间传输

三。相同字节序跨平台的表空间传输(有附上我自己写的更新数据文件头的c程序)



一。 首先来看非跨平台的正常迁移流程。   


1. 自包含检查


connect / sysdba
-- 非严格自包含检查,如对一些表索引不在同一表空间的也能通过检查
exec dbms_tts.transport_set_check('DEMO_DATA',TRUE);
-- 严格自包含检查
exec dbms_tts.transport_set_check('DEMO_DATA',TRUE,TRUE);

--显示检查结果
SELECT * FROM transport_set_check('DEMO_DATA',TRUE);

--如果检查没通过也没关系,Oracle可以多个表空间同时传输。
exec dbms_tts.transport_set_check('DEMO_DATA,DEMO_IDX',TRUE,TRUE);

2. 传输步骤

  2.1 将表空间设为只读


   

alter tablespace users read only;

  2.2 导出表空间


   

exp '/ as sysdba' tablepacespaces=demo_data transport_tablespace=y file=exp_users.dmp;

  2.3 将表空间对应的数据文件转移到目标服务器上。


  2.4 导入表空间


   

imp '/ as sysdba' tablespaces=demo_data transport_tablespace=y file=exp_demo.dmp datafiles='demo_data.dbf' ;

 至此,这个表空间就被包含在了新建的数据库中。


二。不同字节序跨平台的表空间传输
         由于不同平台的字节序不同,导入数据文件时,不同的字节序会导致数据不能被正确的读取。在做网络编程时,
  需要将传输数据转为网络字节序,再在接收端转回来本地字节序。  而 Oracle的数据文件在跨平台进行表空间传输时,同样也需要处理这个麻烦。

    Oracle目前支持一些跨平台的转换,能将文件格式转为目标服务器的字节序格式。
    可在下面SQL中查到支持哪些平台转换:

col PLATFORM_NAME for a40
SELECT * FROM v$transportable_platform;

在数据库中查出当前或目标数据库当前的字节序信息:

col PLATFORM_NAME for a40
SELECT d.platform_name,endian_format
FROM v$transportable_platform tp,v$database d
WHERE tp.platform_name =d.platform_name;



 做个测试:

      将Oracle11gR2的一个表空间从Solaris(64bit)上转到Windows(64bit)上.


      Solaris 为大端方式(Big Endian)


      Windows 为小端方式(Little Endian)



  源平台导出部份:


   1. 建立一个表空间



create tablespace tbs_trans datafile '/u01/app/oracle/oradata/xcldb/tbs_trans_01.dbf' size 1m;
create user usr_trans identified by 111111 default tablespace tbs_trans;
grant connect,resource to usr_trans;
connect usr_trans/111111;
create table tb_trans as select rownum as seq from dual connect by rownum <= 10 ;
select count(*) from tb_trans;
disconnect;
connect / as sysdba
alter tablespace tbs_trans read only;
exit;

2. 导出表空间


exp \'/ as sysdba\' tablespaces=tbs_trans transport_tablespace=y file=/u01/app/exp_tbs_trans.dmp 

 3. 通过RMAN将表空间数据文件转换出一个目标平台字节序相同格式的文件出来


rman target / nocatalog
convert tablespace TBS_TRANS to platform 'Microsoft Windows x86 64-bit' format '/u01/app/%N_%f';
exit;
ls -lt /u01/app/TBS_TRANS*

 4. 将导出的dmp文件(exp_tbs_trans.dmp)与转换后的数据文件(TBS_TRANS_5)都复制到目标平台上。


   


  目标平台导入部份


  5. 把传过来的,之前转换过的数据文件名字变得规范点


set oracle_sid=xcldb
rman target / nocatalog
report schema;
convert datafile 'c:\tmp\TBS_TRANS_5' db_file_name_convert 'c:\tmp\TBS_TRANS_5','C:\ORACLE\ORADATA\XCLDB\TBS_TRANS_01.DBF';

 6. 创建相关用户



create user usr_trans identified by 111111 ;
grant connect,resource to usr_trans;

 7.导入 


imp '/ as sysdba' tablespaces=tbs_trans transport_tablespace=y file=c:\tmp\exp_tbs_trans.dmp datafiles=C:\ORACLE\ORADATA\XCLDB\TBS_TRANS_01.DBF

 8.检查

 select name from v$datafile where name like '%TBS%' ;



select tablespace_name,status from dba_tablespaces;
alter tablespace tbs_trans read write ;

三。相同字节序跨平台的表空间传输


    上面是不同字节序的需要convert tablespace来转换格式,而如果是相同字节序的,是否能直接导出元数据后,再将数据文件复制过去就行了呢。


    比如: Windows与Linux都是小端方式 。


 

  Oracle10g以后,直接复制过去确实是可行的。整库复制过去都可以。
    10g以前的版本就要更改下数据文件头,让两边标识一样。否则imp时会识别不了。


  处理方法:


   

1. 把数据文件头与文件内容分开。 
    2. 把数据文件复制过去,把A平台的数据文件头给抹了,把B平台的数据文件头给取出。
    3.  再将B的数据文件头与A的数据文件内容合并在一起,
就能识别了。


举个9i的例子
     1.  找出文件头大小. 以都有的user表空间数据文件查询即可出来。

Linux 平台:


select file_name,bytes from dba_data_files where tablespace_name='USERS';
!ls -l /u01/app/oracle/oradata/orcl2/users01.dbf

SQL> select file_name,bytes from dba_data_files where tablespace_name='USERS';

FILE_NAME
--------------------------------------------------------------------------------
BYTES
----------
/u01/app/oracle/oradata/orcl2/users01.dbf
5242880

SQL> !ls -l /u01/app/oracle/oradata/orcl2/users01.dbf
-rw-r-----. 1 oracle oinstall 5251072 Feb 22 15:31 /u01/app/oracle/oradata/orcl2/users01.dbf

SQL> select 5251072 - 5242880 from dual;

5251072-5242880
---------------
8192

 Windows 平台:


SQL>  select file_name,bytes from dba_data_files where tablespace_name='USERS';

FILE_NAME
--------------------------------------------------------------------------------
BYTES
----------
C:\ORACLE\ORADATA\XCLDB\USERS01.DBF
5242880


SQL> host dir C:\ORACLE\ORADATA\XCLDB\USERS01.DBF
驱动器 C 中的卷是 OS
卷的序列号是 C8C0-48C4

C:\ORACLE\ORADATA\XCLDB 的目录

2014/02/22 16:06 5,251,072 USERS01.DBF
1 个文件 5,251,072 字节
0 个目录 62,898,417,664 可用字节

SQL> select 5251072 - 5242880 from dual;

5251072-5242880
---------------
8192

 8192 字节即为文件头大小. 两边都相同


 2. 更改文件头


 

 我自己写了个更新文件头的程序。UNIX/Linux下可以用dd命令。

/*************************************************
Author: xiongchuanliang
Description: 更新Oracle数据文件头
**************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define HEADESIZE 8192
#define MAXLEN 200

//将目标端的文件头提取出来
int getDestHeader(const char * fileName,unsigned char *fileHeader);
//用提取出来的文件头替换掉原来的文件头
int updateHeader(const char * fileName,unsigned char *fileHeader);

int main(void)
{
char sourceFile[MAXLEN] = {0};
char headerFile[MAXLEN] = {0};

printf("跨平台表空间传输\n");
printf("同字节序文件的跨平台测试(10g以下)\n");

//"C:\\tmp\\lx\\tbs_trans_01.dbf1"
printf("将从源数据库传到本地临时目录的数据文件路径。\n");
printf("输入源平台数据文件如(C:\\tmp\\lx\\tbs_trans_01.dbf):\n");
scanf ("%199s",sourceFile);

//C:\oracle\oradata\xcldb\USERS01.DBF1
printf("从本地数据文件中任选一个取出文件头,替换源平台原有的文件头。\n");
printf("输入本地数据文件(如C:\\oracle\\oradata\\xcldb\\USERS01.DBF):\n");
scanf ("%199s",headerFile);

unsigned char destHeader[HEADESIZE] = {0};

if( getDestHeader(headerFile,destHeader) == 0)
{
updateHeader(sourceFile,destHeader);
}
return 0;
}

//将目标端的文件头提取出来
int getDestHeader(const char * fileName,unsigned char *fileHeader)
{
if(fileName == NULL) return -1;

//将目标端的文件头提取出来
FILE * fpDest = fopen(fileName,"rb");
if(fpDest== NULL )
{
printf("文件(%s)打开失败!\n",fileName);
return -1;
}
fseek(fpDest,0L,SEEK_SET);
size_t readbyte = fread(fileHeader,1, HEADESIZE,fpDest);
if( readbyte <= 0 )
{
printf("文件头读取失败!\n");
fclose(fpDest);
return -1;
}else if(readbyte != HEADESIZE){
printf("读取出的文件头长度(%d)不够,此事必有蹊跷! 请重新检查。\n",readbyte);
fclose(fpDest);
return -1;
}
printf("文件头大小:%d\n",readbyte);
fclose(fpDest);
return 0;
}

//用提取出来的文件头替换掉原来的文件头
int updateHeader(const char * fileName,unsigned char *fileHeader)
{
FILE * fpSource = fopen(fileName,"rb+");
if(fpSource == NULL )
{
printf("文件(%s)打开失败!\n",fileName);
return -1;
}
fseek(fpSource,0L,SEEK_SET);
size_t writebyte = fwrite(fileHeader,sizeof(fileHeader),1,fpSource);
if( writebyte <= 0 )
{
printf("文件头写入失败!\n");
}else{
printf("已替换到文件%s上.\n",fileName);
printf("文件头替换工作顺利完成!\n");
}
fclose(fpSource);
return 0;
}

   编译运行程序,输入相关的文件合路径,执行,就能将数据文件头更新成本地的信息了,

    至此,imp时就不会报错了。
3. 其它命令和流程与上面的"非跨平台的正常迁移流程"是一样的,关键点就是文件头处理部份不同。


附11gR2支持的平台列表:


SQL>  col PLATFORM_NAME for a40
SELECT * FROM v$transportable_platform;SQL>

PLATFORM_ID PLATFORM_NAME ENDIAN_FORMAT
----------- ---------------------------------------- --------------
1 Solaris[tm] OE (32-bit) Big
2 Solaris[tm] OE (64-bit) Big
7 Microsoft Windows IA (32-bit) Little
10 Linux IA (32-bit) Little
6 AIX-Based Systems (64-bit) Big
3 HP-UX (64-bit) Big
5 HP Tru64 UNIX Little
4 HP-UX IA (64-bit) Big
11 Linux IA (64-bit) Little
15 HP Open VMS Little
8 Microsoft Windows IA (64-bit) Little

PLATFORM_ID PLATFORM_NAME ENDIAN_FORMAT
----------- ---------------------------------------- --------------
9 IBM zSeries Based Linux Big
13 Linux x86 64-bit Little
16 Apple Mac OS Big
12 Microsoft Windows x86 64-bit Little
17 Solaris Operating System (x86) Little
18 IBM Power Based Linux Big
19 HP IA Open VMS Little
20 Solaris Operating System (x86-64) Little
21 Apple Mac OS (x86-64) Little

20 rows selected.


另个关于字节序对网络编程的影响可以看看这篇<<​​网络编程(7)字节序对跨平台数据传输的影响​​>>