三.导入/导出与字符集转换
3.1 EXP/IMP Export 和 Import 是一对读写Oracle数据的工具。Export 将 Oracle 数据库中的数据输出到操作系统文件中, Import 把这些文件中的数据读到Oracle 数据库中,由于使用exp/imp进行数据迁移时,数据从源数据库到目标数据库的过程中有四个环节涉及到字符集,如果这四个环节的字符集不一致,将会发生字符集转换。
EXP ____________ _________________ _____________ |imp导入文件|<-|环境变量NLS_LANG|<-|数据库字符集| ------------ ----------------- -------------
IMP ____________ _________________ _____________ |imp导入文件|->|环境变量NLS_LANG|->|数据库字符集| ------------ ----------------- -------------
四个字符集是 (1)源数据库字符集 (2)Export过程中用户会话字符集(通过NLS_LANG设定) (3)Import过程中用户会话字符集(通过NLS_LANG设定) (4)目标数据库字符集
3.2导出的转换过程 在Export过程中,如果源数据库字符集与Export用户会话字符集不一致,会发生字符集转换,并在导出文件的头部几个字节中存储Export用户会话字符集的ID号。在这个转换过程中可能发生数据的丢失。 例:如果源数据库使用ZHS16GBK,而Export用户会话字符集使用US7ASCII,由于ZHS16GBK是16位字符集,而US7ASCII是7位字符集,这个转换过程中,中文字符在US7ASCII中不能够找到对等的字符,所以所有中文字符都会丢失而变成“?? ”形式,这样转换后生成的Dmp文件已经发生了数据丢失。 因此如果想正确导出源数据库数据,则Export过程中用户会话字符集应等于源数据库字符集或是源数据库字符集的超集
3.3导入的转换过程 (1)确定导出数据库字符集环境 通过读取导出文件头,可以获得导出文件的字符集设置 (2)确定导入session的字符集,即导入Session使用的NLS_LANG环境变量 (3)IMP读取导出文件 读取导出文件字符集ID,和导入进程的NLS_LANG进行比较 (4)如果导出文件字符集和导入Session字符集相同,那么在这一步骤内就不需要转换,如果不同,就需要把数据转换为导入Session使用的字符集。可以看出,导入数据到数据库过程中发生两次字符集转换 第一次:导入文件字符集与导入Session使用的字符集之间的转换,如果这个转换过程不能正确完成,Import向目标数据库的导入过程也就不能完成。 第二次:导入Session字符集与数据库字符集之间的转换。 然而,oracle8i的这种转换只能在单字节字符集之间进行,oracle8i导入Session不支持多字节字符集之间的转换,因此为了避免第一次转换,导入Session使用的NLS_LANG与导出文件字符集相同,第二次转换(通过SQL*Net)支持任何两种字符集。以上情况在Oracle9i中略有不同
四.乱码问题
oracle在数据存储、迁移过程中经常发生字符乱码问题,归根到底是由于字符集使用不当引起。下面以使用客户端sqlplus向数据库插入数据和导入/导出(EXP/IMP)过程为例,说明乱码产生的原因。
4.1使用客户端sqlplus向数据库存储数据 这个过程存在3个字符集设置 (1)客户端应用字符集 (2)客户端NLS_LANG参数设置 (3)服务器端数据库字符集(Character Set)设置 客户端应用sqlplus中能够显示什么样的字符取决于客户端操作系统语言环境(客户端应用字符集),但在应用中录入这些字符后,这些字符能否在数据库中正常存储,还与另外两个字符集设置紧密相关,其中客户端NLS_LANG参数主要用于字符数据传输过程中的转换判断。常见的乱码大致有两种情形: (1)汉字变成问号“?”; 当从字符集A 转换成字符集B时,如果转换字符之间不存在对应关系,NLS_LANG使用替代字符“?”替代无法映射的字符 (2)汉字变成未知字符(虽然有些是汉字,但与原字符含义不同) 转换存在对应关系,但字符集A 中的字符编码与字符集B 中的字符编码代表不同含义
4.2发生乱码原因 乱码产生是由于几个字符集之间转换不匹配造成,分以下几种情况: (注:字符集之间如果不存在子集、超集对应关系时的情况不予考虑,因为这种情况下字符集之间转换必产生乱码) 1)服务器端数据库字符集与客户端应用字符集相同,与客户端NLS_LANG参数设置不同 如果客户端NLS_LANG字符集是其它两种字符集的子集,转换过程将出现乱码。 解决方法:将三种字符集设置成同一字符集,或NLS_LANG字符集是其它两种字符集的超集 2)服务器端数据库字符集与客户端NLS_LANG参数设置相同,与客户端应用字符集不同 如果客户端应用字符集是其它两种字符集的超集时,转换过程将出现乱码,但对于单字节编码存储中文问题,可参看本文第5章节的分析 3)客户端应用字符集、客户端NLS_LANG参数设置、服务器端数据库字符集互不相同 此种情况较为复杂,但三种字符集之间只要有不能转换的字符,则必产生乱码
4.3导入/导出过程出现乱码原因 这个过程存在4个字符集设置,在3.1章节中已分析 (1)源数据库字符集 (2)EXP过程中NLS_LANG参数 (3)IMP过程中NLS_LANG参数 (4)目标数据库字符集 出现乱码原因 1)当源数据库字符集不等于EXP过程中NLS_LANG参数,且源数据库字符集是EXP过程中NLS_LANG的子集,才能保证导出文件正确,其他情况则导出文件字符乱码 2)EXP过程中NLS_LANG字符集不等于IMP过程中NLS_LANG字符集,且EXP过程中NLS_LANG字符集是IMP过程中NLS_LANG字符集的子级, 才能保证第一次转换正常,否则第一次转换中出现乱码。 3)如果第一次转换正常,IMP过程中NLS_LANG字符集是目标数据库字符集的子集或相同,才能保证第二次转换正常,否则则第二次转换中出现乱码
五.单字节编码存储中文问题
由于历史的原因,早期的oracle没有中文字符集(如oracle6、oracle7、oracle7.1),但有的用户从那时起就使用数据库了,并用US7ASCII字符集存储了中文,或是有的用户在创建数据库时,不考虑清楚,随意选择一个默认的字符集,如WE8ISO8859P1或US7ASCII,而这两个字符集都没有汉字编码,虽然有些时候选用这种字符集好象也能正常使用,但用这种字符集存储汉字信息从原则上说就是错误的,它会给数据库的使用与维护带来一系列的麻烦。 正常情况下,要将汉字存入数据库,数据库字符集必须支持中文,而将数据库字符集设置为US7ASCII等单字节字符集是不合适的。US7ASCII字符集只定义了128个符号,并不支持汉字。另外,如果在SQL*PLUS中能够输入中文,操作系统缺省应该是支持中文的,但如果在NLS_LANG中的字符集设置为US7ASCII,显然也是不正确的,它没有反映客户端的实际情况。但在实际应用中汉字显示却是正确的,这主要是因为Oracle检查数据库与客户端的字符集设置是同样的,那么数据在客户与数据库之间的存取过程中将不发生任何转换,但是这实际上导致了数据库标识的字符集与实际存入的内容是不相符的。而在SELECT的过程中,Oracle同样检查发现数据库与客户端的字符集设置是相同的,所以它也将存入的内容原封不动地传送到客户端,而客户端操作系统识别出这是汉字编码所以能够正确显示。 在这个例子中,数据库与客户端都没有设置成中文字符集,但却能正常显示中文,从应用的角度看好象没问题。然而这里面却存在着极大的隐患,比如在应用length或substr等字符串函数时,就可能得到意外的结果。 对于早期使用US7ASCII字符集数据库的数据迁移到oracle8i/9i中(使用zhs16gbk),由于原始数据已经按照US7ASCII格式存储,对于这种情况,可以通过使用Oracle8i的导出工具,设置导出字符集为US7ASCII,导出后使用UltraEdit等工具打开dmp文件,修改第二、三字符,修改 0001 为0354,这样就可以将US7ASCII字符集的数据正确导入到ZHS16GBK的数据库中。
六.结束语
为了避免在数据库迁移过程中由于字符集不同导致的数据损失,oracle提供了字符集扫描工具(character set scanner),通过这个工具我们可以测试在数据迁移过程中由于字符集转换可能带来的问题,然后根据测试结果,确定数据迁移过程中最佳字符集解决方案。
参考文献 [1]Biju Thomas , Bob Bryla《oracle9i DBA基础I 学习指南》电子工业出版社 2002
|