AL32UTF8 Unicode UTF-8通用字符集
ZHS16GBK GBK 16位简体中文
ZHT16HKSCS MS Windows码页950,包含香港增补字符集
ZHT16MSWIN950 MS Windows码页950繁体中文
ZHT32EUC EUC 32位繁体中文
国家字符集:AL18UTF16 Unicode UTF-16通用字符集
国家字符集:UTF8 Unicode 3.0 UTF-8通用字符集,兼容CESU-8
NLS_LANGUAGE 默认语言
NLS_TERRITORY 默认地区
最常见到的字符集转换是在EXP/IMP过程中。在一个完整的EMP/IMP过程,可能发生4次字符集转换:
1. 源数据库字符集.
2. Export过程中用户会话字符集 (通过NLS_LANG设定).
3. Import过程中用户会话字符集 (通过NLS_LANG设定).
4. 目标数据库字符集.
以下简单描述一下可能的字符集转换:
1. 导出的转换过程:
在Export过程中,如果源数据库字符集与Export用户会话字符集不一致,会发生字符集转换,并在导出文件的头部几个字节中存储Export用户会话字符集的ID号。在这个转换过程中可能发生数据的丢失,例如:如果源数据库使用ZHS16GBK,而Export用户会话字符集使用US7ASCII; 由于ZHS16GBK是16位字符集,而US7ASCII是7位字符集,这个转换过程中,中文字符在US7ASCII中不能够找到对等的字符,所以所有中文字符都会丢失而变成“??”形式,这样转换后生成的DMP文件已经发生了数据丢失。因此如果想正确导出源数据库数据,则Export过程中用户会话字符集应等于源数据库字符集或是源数据库字符集的超集。
2. 导入的转换过程
确定导出数据库字符集环境,通过读取导出文件头,可以获得导出文件的字符集设置。确定导入session的字符集,即导入Session使用的 NLS_LANG环境变量。IMP读取导出文件,读取导出文件字符集ID,和导入进程的NLS_LANG进行比较。如果导出文件字符集和导入 Session字符集相同,那么在这一步骤内就不需要转换,如果不同,就需要把数据转换为导入Session使用的字符集。可以看出,导入数据到数据库过程中发生两次字符集转换:第一次,导入文件字符集与导入Session使用的字符集之间的转换,如果这个转换过程不能正确完成,Import向目标数据库的导入过程也就不能完成;第二次:导入Session字符集与数据库字符集之间的转换。
3. 试验
数据库Oracle 10.2.0.3,数据库字符集是AL32UTF8;客户端就是同一台机器的XP。
SQL> create table test(col1 number(1),col2 varchar2(10));
--session 1 设置客户端字符集为ZHS16GBK(NLS_LANG = ZHS16GBK)
SQL> insert into test values(1,'中国'); --1为session 1的标记
SQL> commit;
--session 2 设置客户端字符集AL32UTF8(NLS_LANG = AL32UTF8)
SQL> insert into test values(2,'中国'); --2为session 2的标记
SQL> commit;
--session 1
SQL> select * from test;
1 中国
2 ??
--session 2
SQL> select * from test;
1 ??
2 中国
从session 1和session 2的结果中可以看到,相同的字符在不同的字符集输入环境下,显示成了乱码。在ZHS16GBK字符集的客户端,我们看到了UTF8字符集客户端输入的相同的中文变成了乱码;在UTF8字符集客户端,我们看到ZHS16GBK字符集的客户端输入的中文变成了乱码。
--session 1 或 session 2
SQL> select col1, dump(col2,1016) from test;
1 Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd
2 Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
SQL> select dump('中',1016) from dual;
Typ=96 Len=3 CharacterSet=AL32UTF8: e4,b8,ad
SQL> select dump('国',1016) from dual;
Typ=96 Len=3CharacterSet=AL32UTF8: e5,9b,bd
让我们重新来理一下思路,并提出几个问题:
1. 为什么显示为相同的字符,存储到数据库中却变成了不同的编码?
2. 我们在向数据库中插入数据的时候,oracle究竟做了些什么?
3. 操作系统字符集,客户端字符集,数据库字符集究竟是什么关系?
获得windows字符集(codepage):chcp
--session 1 或 session 2
SQL> select col1, dump(col2,1016) from test;
1 Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd
2 Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
SQL> select col1, dump(col2,1016) from t1;
1 Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd
2 Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
--session 1
SQL> insert into t1 values('中国',1);
SQL> commit;
SQL> select * from t1;
中国 1
??? 2
--session 2
SQL> insert into t1 values('中国',2);
SQL> commit;
SQL> select * from t1;
涓?浗 1
中国 &nb
session 1:我们看到session 2输入的字符"中国"变成了乱码"???"。
session 2:我们看到session 1输入的字符"中国"变成了另外的字符"涓?浗"。
下面我们来分析一下在这中间,数据库、客户端和操作系统都发生了那些事情。上面已经讨论了:
1) session 1输入的字符"中国",在数据库中存储的字符编码为”e4,b8,ad,e5,9b,bd";session 2输入的字符"中国",在数据库中存储的字符编码为”d6,d0,b9,fa"。
2) 当session 1开始查询时,Oracle从表中取出这两个字符,并按照字符集AL32UTF8和字符集ZHS16GBK的编码映射表,将它的转换成ZHS16GBK字符编码。对于编码“e4,b8,ad,e5,9b,bd”,它对应的ZHS16GBK的字符编码为"d6,d0,b9,fa",这个编码对应的字符为“中国”,所以我们就看到这个字符被正常显示。而对于字符集AL32UTF8字符编码“d6,d0,b9,fa”,由于我们用于显示字符的windows 环境使用的是ZHS16GBK字符集,而在ZHS16GBK字符集里面并没有对应这个编码的字符或者属于无法显示的符号,于是使用了"?"这样的字符来替换,这就是为什么我们看到session 2输入的字符变成了这样的乱码。
3) 当session 2开始查询时,Oracle从表中取出这两个字符,由于客户端和数据库的字符集设置一致,Oracle将忽略字符的转换问题,于是直接将数据库中存储的字符返回给客户端。对于编码为"d6,d0,b9,fa"的字符,返回给客户端,而客户端显示所用的字符集正好是ZHS16GBK,在这个字符集里,这个编码对应的是"中国"两个字符,所以就正常显示出来了。对于字符编码“e4,b8,ad,e5,9b,bd”,返回到客户端后,因为在 ZHS16GBK里采用的是双字节存储字符方式,所以这6字节对应了ZHS16GBK字符集的3个字符,也就是我们看到的"涓?浗"。
到现在为止,我想我们基本上搞清楚了为什么日常查询时会遇到乱码的问题。其实乱码,说到底就是用于显示字符的操作系统没有在字符编码中找到对应的字符导致的,造成这种现象的主要原因就是:
1) 输入操作的OS字符编码和查询的OS字符编码不一致导致出现乱码。
2) 输入操作的客户端字符集NLS_LANG和查询客户端字符集NLS_LANG不同,也可能导致查询返回乱码或者错误的字符。
还有一个问题需要解释一下:在上面的例子中,相同的字符在不同的字符集中对应着不同的字符编码,这个通常称为字符集不兼容或者不完全兼容,比如ZHS16GBK和AL32UTF8。它们存储的ASCII码的字符编码都是相同的,但对于汉字却是不同的。如果两个字符集对于相同的字符采用的相同的字符编码,我们称之为字符兼容,范围大的叫做范围小的字符集的超集。我们通常遇到的ZHS16CGB231280和ZHS16GBK就是这样的情况,后者是前者的超集。
一个误区:看到很多人在出现乱码的时候都首先要做的就是将客户端字符集设置和数据库一致,其实这是没有太多根据的。设想一下,假如数据库字符集是AL32UTF8,里面存储这一些中文字符,而我的客户端操作系统是英文的,此时我将客户端的NLS_LANG设置成 AL32UTF8,这样会解决问题吗?这样客户端就能显示中文了吗?客户端就能输入中文了吗?现在客户端是英文的,它的字符集里根本就没有汉字的编码,我们简单的修改一下客户端的字符集又有什么用?前面已经讨论了,这个设置无非就是告诉ORACLE我将以什么样的字符集与数据库进行数据交换,对于解决乱码问题毫无关系。
正确的做法是将客户端的操作系统改成支持中文字符,并将客户端字符集改成和操作系统一致的字符集,这样才能真正的解决问题。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/5359/viewspace-626717/ ,如需转载,请注明出处,否则将追究法律责任。