3.7 识别导出文件的字符集
在传统的 EXP 导出文件中,记录着导出使用的字符集 id,通过查看导出文件头的第2、3个字节,我们可以找到十六进制表示的字符集ID,在Windows上,可以使用UltraEdit等工具打开dmp文件,查看其导出字符集,如图3-6所示。
图3-6 导出字符集
在UNIX环境上,可以通过以下命令来查看(以下范例来自Solaris平台),如图3-7所示。
图3-7 查看字符集
cat expdat.dmp | od -x | head
需要注意的是,在不同平台,以上命令的输出可能有所不同,比如在Linux平台:
[oracle@jumper oracle]$ cat a.dmp |od -x |head -2
0000000 0303 4554 5058 524f 3a54 3156 2e30 3230
0000020 302e 0a31 4544 4759 454c 520a 4154 4c42
这是由于Solaris和Linux的字节序是不同的,Solaris是Big-Endians,Linux是Little-Endians,所以Linux上的输出通过交换可以得到:
0303 5445 5850 4f52…
字符集和Solaris上是一致的,所以也可以通过od的参数设置显示格式:
[oracle@jumper oracle]$ cat a.dmp|od -t x1|head -2
0000000 03 03 54 45 58 50 4f 52 54 3a 56 31 30 2e 30 32
0000020 2e 30 31 0a 44 45 59 47 4c 45 0a 52 54 41 42 4c
Oracle提供标准函数,对字符集名称及ID进行转换:
SQL> select nls_charset_id('ZHS16GBK') from dual;
NLS_CHARSET_ID('ZHS16GBK')
--------------------------
852
SQL> select nls_charset_name(852) from dual;
NLS_CHAR
--------
ZHS16GBK
十进制转换十六进制,即可获得字符集的编码:
SQL> select to_char('852','xxxx') from dual;
TO_CH
-----
354
对应图3-6或图3-7中的第2、3字节,就知道该导出文件字符集为ZHS16GBk。
查询数据库中有效的字符集可以使用以下脚本:
col nls_charset_id for 9999
col nls_charset_name for a30
col hex_id for a20
select nls_charset_id(value) nls_charset_id,
value nls_charset_name,to_char(nls_charset_id(value), 'xxxx') hex_id
from v$nls_valid_values where parameter = 'CHARACTERSET'
order by nls_charset_id(value);
输出样例如下:
NLS_CHARSET_ID NLS_CHARSET_NAME HEX_ID
-------------- ------------------------------ -------------
1 US7ASCII 1
2 WE8DEC 2
……
850 ZHS16CGB231280 352
851 ZHS16MACCGB231280 353
852 ZHS16GBK 354
853 ZHS16DBCS 355
……
868 ZHT16HKSCS 364
870 AL24UTFFSS 366
871 UTF8 367
而对于Oracle 10g的expdp导出文件,一切则要简单得多,在expdp的导出文件开始部分, Oracle以XML格式记录了数据的字符集信息,以下是一个单表导出文件的头信息。
在文件开始部分就记录了数据库的字符集、国家字符集及时区等信息,在表字段的属性部分也记录了字符集ID:
<?xml version="1.0"?>
<ROWSET><ROW>
<STRMTABLE_T>
<VERS_MAJOR>1</VERS_MAJOR>
<VERS_MINOR>0 </VERS_MINOR>
<VERS_DPAPI>3</VERS_DPAPI>
<ENDIANNESS>2</ENDIANNESS>
<CHARSET>ZHS16GBK</CHARSET>
<NCHARSET>AL16UTF16</NCHARSET>
<DBTIMEZONE>+08:00</DBTIMEZONE>
<FDO>0000006001240F050B0C0....</FDO>
<OBJ_NUM>79220</OBJ_NUM>
<OWNER_NAME>EYGLEE</OWNER_NAME>
<NAME>TEST</NAME>
<PROPERTY>536870912</PROPERTY>
<COL_LIST>
<COL_LIST_ITEM>
<OBJ_NUM>79220</OBJ_NUM>
<COL_NUM>1</COL_NUM>
<INTCOL_NUM>1</INTCOL_NUM>
<SEGCOL_NUM>1</SEGCOL_NUM>
<PROPERTY>0</PROPERTY>
<NAME>NAME</NAME>
<TYPE_NUM>1</TYPE_NUM>
<LENGTH>20</LENGTH>
<NOT_NULL>0</NOT_NULL>
<CHARSETID>852</CHARSETID>
<CHARSETFORM>1</CHARSETFORM>
<CHARLENGTH>20</CHARLENGTH>
</COL_LIST_ITEM>
</COL_LIST>
</STRMTABLE_T>
</ROW></ROWSET>
对于传统的DMP导出文件,在很多时候,当进行导入操作时,已经离开了源数据库,这时如果目标数据库的字符集和导出文件不一致,多半就需要进行特殊处理进行转换。最常见的转换发生在从US7ASCII到ZHS16GBK之间。
由于很多数据库最初以US7ASCII字符集存储中文,单纯通过导出导入是无法完成字符集转换的。对于这种情况,可以通过设置导出字符集为US7ASCII,原样导出数据;导出后修改导出文件的第2、3 字符,修改 0001 为 0354,这样就可以将 US7ASCII 字符集的数据正确导入到ZHS16GBK的数据库中。
如图3-8所示是一个测试例子,我们可以通过UltraEdit等工具的二进制编辑模式修改导出文件:
图3-8 修改后的字符集
修改完成之后,可以导入修改后的DMP文件:
E:\nls2>set NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
E:\nls2>imp eygle/eygle file=Sus7ascii-Cus7ascii-exp817.dmp fromuser=eygle touser=eygle tables=test
Export file created by EXPORT:V08.01.07 via conventional path
import done in ZHS16GBK character set and AL16UTF16 NCHAR character set
export server uses UTF8 NCHAR character set (possible ncharset conversion)
. . importing table "TEST" 2 rows imported
Import terminated successfully without warnings.
通过这种方式,最终中文可以被正常导入ZSH16GBK的数据库:
E:\nls2>sqlplus eygle/eygle
SQL*Plus: Release 9.2.0.4.0 - Production on Mon Nov 3 17:37:23 2003
Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
SQL> select name,dump(name) from test;
NAME DUMP(NAME)
-------- ------------------------------------------------------------------------
测试 Typ=1 Len=4: 178,226,202,212
Test Typ=1 Len=4: 116,101,115,116
2 rows selected.
另外一种可以尝试的方法是使用 create database 命令。如果导出文件使用的字符集是US7ASCII,目标数据库的字符集是 ZHS16GBK,就可以使用 create database 的方法来修改,具体操作如下:
SQL> select * from v$nls_parameters;
PARAMETER VALUE
------------------------------ ------------------------------
NLS_LANGUAGE AMERICAN
NLS_TERRITORY AMERICA
NLS_CURRENCY $
NLS_ISO_CURRENCY AMERICA
NLS_NUMERIC_CHARACTERS .,
NLS_CALENDAR GREGORIAN
NLS_DATE_FORMAT DD-MON-RR
NLS_DATE_LANGUAGE AMERICAN
NLS_CHARACTERSET ZHS16GBK
NLS_SORT BINARY
……
19 rows selected.
SQL> create database character set us7ascii;
create database character set us7ascii
*
ERROR at line 1:
ORA-01031: insufficient privileges
SQL> select * from v$nls_parameters;
PARAMETER VALUE
------------------------------ ------------------------------
NLS_LANGUAGE AMERICAN
NLS_TERRITORY AMERICA
NLS_CURRENCY $
NLS_ISO_CURRENCY AMERICA
NLS_NUMERIC_CHARACTERS .,
NLS_CALENDAR GREGORIAN
NLS_DATE_FORMAT DD-MON-RR
NLS_DATE_LANGUAGE AMERICAN
NLS_CHARACTERSET US7ASCII
NLS_SORT BINARY
……
19 rows selected.
然后可以导入数据:
E:\nls2>set nls_lang=AMERICAN_AMERICA.US7ASCII
E:\nls2>imp eygle/eygle file=Sus7ascii-Cus7ascii.dmp fromuser=eygle touser=eygle
Export file created by EXPORT:V09.02.00 via conventional path
import done in US7ASCII character set and AL16UTF16 NCHAR character set
import server uses ZHS16GBK character set (possible charset conversion)
. . importing table "TEST" 2 rows imported
Import terminated successfully without warnings.
查询导入数据:
E:\nls2>sqlplus eygle/eygle
SQL> select * from test;
NAME
----------
测试
test
当发出“create database character set us7ascii;”命令时,数据库v$nls_parameters中的字符集设置随之更改,该参数影响导入进程,更改后可以正确导入数据,重起数据库后,该设置恢复。
提示:v$nls_paraemters 来源于 x$nls_parameters ,该动态性能视图影响导入操作;而nls_database_ parameters 来源于 props$数据表,影响数据存储。以上的方法只应该在不得已的情况下使用,其本质是欺骗数据库,强制导入数据,但是可能会损失元数据。