欢迎收看视频: http://www.boobooke.com/v/bbk4011

http://www.boobooke.com/v/bbk4012

 

 

Oracle的字符集
 
Oracle字符集的基础知识
GBK字符集是在大陆和新加坡的电脑中最常用的汉字字符集,而在台湾、澳门和香港通常使用的字符集是BIG5,俗称大五码。这两种字符集都是双字节的。
Unicode是unique code的缩写,Unicode字符集的出现是为了解决不同字符集之间的兼容和转换的问题,Unicode协会成立于1991年,并推出了Unicode字符集。Unicode字符集有3个主要的编码:UTF-8、USC-2和UTF-16,最常用的是UTF-8。请注意,字符集和字符编码不是一回事,大多数字符集只有一种编码方式,并且字符集和字符编码的名字是相同的,但Unicode是有多种编码方式的。
需要强调的是UTF-8是一种变长的编码方式,以8位为单位对Unicode进行编码,占位从8到32个位,越常用的字符占位越少。假设不采用变长的编码方式,连最常用的ASCII码也是32位,可以想像效率是多么糟糕!
UTF-8编码有以下几种格式:
00000 – 0007F:0xxxxxxx
00080 – 0007FF:110xxxxx 10xxxxxx
00800 – 00FFFF:1110xxxx 10xxxxxx 10xxxxxx
10000 – 10FFFF:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
Unicode组织把中、日、韩三国的文字进行了整合,整合后的字符集称为CJK。在UTF-8编码中一个字符占3个字节(byte),也就是24个位(bit)。
 
Oracle字符集的命名约定如下:
地区+字符的位数+字符集名字
这里的字符的位数的单位是bit,俗称小B,不是byte,俗称大B。
例如:
US7ASCII 表示7位的美国代码,可以定义128个字符,也就是大家熟知的ASCII码(美国信息交换标准代码)。
WE8ISO8859P1 表示8位的西欧字符集,可以定义256个字符,适合于欧洲大部分国家,ISO标准8859P1编码。
AL32UTF8 这里的AL就是表示所有地区了,32位的UTF-8的编码。
ZHS16GBK表示16位的简体中文编码。
当一种字符集的编码数值包含所有另一种字符集的编码数值,并且两种字符集相同编码数值代表相同的字符时,则前者是后者的超级,或称后者是前者的子集。官方文档中有子集-超级对照表(subset-superset pairs),由于US7ASCII是最早的Oracle数据库编码格式,因此有许多字符集是US7ASCII的超集,例如WE8ISO8859P1、AL32UTF8、ZHS16GBK都是US7ASCII的超集。子集和超集的字符编码相同,因此子集和超集之间不会进行字符编码的转换。
在中国大陆,目前实际使用得比较多的是AL32UTF8和ZHS16GBK这两种字符集。关于AL32UTF8和ZHS16GBK这两种字符集的区别,最需要注意的问题是,一个汉字在AL32UTF8中占3个字节,而在ZHS16GBK中只占2个字节。
下面是忽视了这个问题时常见的错误,数据库的字符集是AL32UTF8:
SQL> select * from nls_database_parameters where parameter='NLS_CHARACTERSET';
PARAMETER                 VALUE
------------------------- --------------------
NLS_CHARACTERSET          AL32UTF8
创建一个两字节的字段,然后插入一个汉字:
SQL> create table test (ch char(2));
 
Table created.
 
SQL> insert into test values ('一');
insert into test values ('一')
                         *
ERROR at line 1:
ORA-12899: value too large for column "SYS"."TEST"."CH" (actual: 3, maximum: 2)
要把占三个字节的汉字插入到两个字节的字段中,当然不成功,但同样的insert语句在采用ZHS16GBK做字符集的数据库中是可以成功的。
再看看一个汉字“一”在这个数据库中的实际存储内容。
 
SQL> select dump('一',1016) from dual;
 
DUMP('一',1016)
----------------------------------------------------------------------------------------
Typ=96 Len=3 CharacterSet=AL32UTF8: e4,b8,80
可以看到汉字“一”存储的16进制内码是“e4b880”。
使用Oracle提供的查看字符集的工具:LBUILDER看看“一”在AL32UTF8中的内码:
 
图1-3   AL32UTF8中的汉字“一”
可以看到汉字“一”在AL32UTF8中的内码是“e4b880”。
再看看“一”在ZHS16GBK中的内码:
SQL> select dump(convert('一','ZHS16GBK'),1016) from dual;
 
DUMP(CONVERT('一','ZHS16GBK'),1016)
--------------------------------------------------------------------------------
Typ=1 Len=2 CharacterSet=AL32UTF8: d2,bb
再使用LBUILDER查看“一”在ZHS16GBK中的内码:
 
图1-4   ZHS16GBK 中的汉字“一”
结果也是“D2BB”。
在用dbca创建oracle 数据库的创建过程中,oracle会根据服务器操作系统的语言设置来推荐默认的字符集。以oracle 11g在linux下的安装为例,如果将环境变量LANG设置为英语:
$ export LANG=en_US.UTF-8
会出现下面的界面用于选择Oracle的字符集:
 
图1-1   DBCA中的英文字符集界面
 
如果设置为中文:
$ export LANG=zh_CN.UTF-8
会相应的出现下面的界面:
 
图1-2   DBCA中的中文字符集界面
数据库一旦建立,字符集不能轻易改变,修改字符集是一项高风险的工作,因此创建数据库时选择恰当的字符集就十分必要,选择字符集需要考虑以下的因素:
1.         数据库现在和将来需要支持的语言种类;
2.         客户端的字符集,如果客户端的字符集和数据库不一样,将会发生字符转换,会影响效率甚至造成数据丢失;
3.         ZHS16GBK中的汉字占两个字节,相比AL32UTF8要节省存储空间并会提高效率;
4.         和这个数据库有数据交换的其它数据库的字符集,在数据交换中不同的字符集也会发生字符转换。
 
Oracle的乱码是DBA在实际工作中经常遇到的问题。要避免乱码的出现,DBA必需对应用环境的字符集非常清楚,这里的应用环境指的是,数据库的字符集和客户端的字符集。一定要正确设置客户端的环境变量NLS_LANG。
NLS_LANG 参数格式:
    NLS_LANG=LANGUAGE_TERRITORY.Client CHARACTERSET
    Language: 指定oracle消息使用的语言,校验,日期的显示;
    Territory :地区名,指定默认日期、数字、货币等格式
    Client characterset :指定客户端将使用的字符集,此项的设置特别关键,乱码的产生通常都是这一项设置不对造成的。
例如:
NLS_LANG=AMERICAN_AMERICA.US7ASCII,语言是AMERICAN,地区是AMERICA,客户端字符集是US7ASCII;
NLS_LANG="simplified chinese"_china.zhs16gbk,语言是简体中文,地区是中国,客户端字符集是ZHS16GBK。
避免乱码的关键是要正确判断客户端的字符集,然后正确设置NLS_LANG。先来看看windows的pagecode:
C:\>chcp
活动的代码页: 936
Pagecode就是各种字符集和Unicode之间的字符映射表,例如,我们从windows的控制面板中可以看到:GBK和Unicode之间的字符映射表是936,Big5和Unicode之间的字符映射表是950。
 
图1-5 Pagecode
因此可以断定windows客户端的字符集是GBK。但还有一个简单而且直观的判定方法,就是输入一个汉字,然后再查看这个汉字的二进制内码,例如在UtraEdit中输入一个汉字“一”,再用二进制查看,发现这个汉字的内码是“D2BB”,也就是GBK的字符集。
在linux可以用下面的命令查看:
$ echo | od -t x1
0000000 d2 bb 0a
0000003
下面做一个实验,来看看不同的客户端字符集和不同的NLS_LANG的设置会在数据库中插入什么样的实际字符值:
数据库服务器的字符集是AL32UTF8,先创建一个测试表:
SQL>create table test (client_char char(13),nls_lang char(15),ch varchar2(6));
表中第一个字段表示客户端的字符集,第二个字段代表NLS_LANG中设置的客户端字符集,第三个字段是插入的汉字。
先用windows下的cmd.exe中运行sqlplus,检查一下cmd的字符设置,鼠标右键查看cmd.exe的属性:
 
图1-6 cmd.exe的属性
可以看出这个客户端的字符集是GBK。
C:\>set NLS_LANG=simplified chinese_china.ZHS16GBK
C:\>sqlplus sys/sys@blade1/odd as sysdba
 
SQL*Plus: Release 10.2.0.1.0 - Production on 星期一 1 25 13:53:43 2010
 
Copyright (c) 1982, 2005, Oracle. All rights reserved.
 
 
连接到:
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options
 
SQL> select userenv('language') from dual;
USERENV('LANGUAGE')
------------------------------------------------------------------------------
SIMPLIFIED CHINESE_CHINA.AL32UTF8
SQL>
SQL> create table test (client_char char(13),nls_lang char(15),ch varchar2(6));
 
表已创建。
 
SQL> insert into test values('GBK','ZHS16GBK','');
 
已创建 1 行。
 
SQL> commit;
 
提交完成。
 
SQL> quit
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options 断开
 
C:\>set NLS_LANG=simplified chinese_china.AL32UTF8
C:\>sqlplus sys/sys@blade1/odd as sysdba
 
SQL*Plus: Release 10.2.0.1.0 - Production on 鏄熸湡涓€ 1?25 13:59:18 2010
 
Copyright (c) 1982, 2005, Oracle. All rights reserved.
 
 
杩炴帴鍒?
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options
 
SQL> insert into test test values('GBK','AL32UTF8','');
 
宸插垱寤?1 琛屻€
 
SQL> commit ;
 
鎻愪氦瀹屾垚銆
 
SQL> quit
?Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options 紑
从上面的显示已经可以看出,当客户端设置成AL32UTF8时,Oralce的提示已经就乱码了。再在putty中运行sqlplus,先把putty的字符集设置成UTF-8:
 

图1-7 putty中的字符集的设置
再用查看汉字内码的方法检查一下设置。
$ echo | od -t x1
0000000 e4 b8 80 0a
0000004
汉字“一”在UTF-8中的内码是“e4b880”,可以看到设置已经生效。
$ export NLS_LANG=.AL32UTF8
$ sqlplus / as sysdba
 
SQL*Plus: Release 10.2.0.1.0 - Production on Mon Jan 25 14:15:58 2010
 
Copyright (c) 1982, 2005, Oracle. All rights reserved.
 
 
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options
 
SQL> insert into test values('UTF-8','AL32UTF8','');                     
1 row created.
 
SQL> commit;
 
Commit complete.
 
SQL> quit
Disconnected from Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options
 
[oracle@blade1 ~]$ export NLS_LANG=.ZHS16GBK
[oracle@blade1 ~]$ sqlplus / as sysdba
 
SQL*Plus: Release 10.2.0.1.0 - Production on Mon Jan 25 14:21:47 2010
 
Copyright (c) 1982, 2005, Oracle. All rights reserved.
 
 
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options
 
SQL> insert into test values('UTF-8','ZHS16GBK','');
 
1 row created.
 
SQL> commit;
 
Commit complete.
 
 
 
现在来看看这4行插入的结果,当然首先得把NLS_LANG的变量设置正确:
SQL> col dump_ch for a30
SQL> select client_char,nls_lang,ch,dump(ch,1016) dump_ch from test
CLIENT_CHAR   NLS_LANG        CH     DUMP_CH
------------- --------------- ------ ------------------------------
GBK           ZHS16GBK             Typ=1 Len=3 CharacterSet=AL32U
                                     TF8: e4,b8,80
 
GBK           AL32UTF8        һ      Typ=1 Len=2 CharacterSet=AL32U
                                     TF8: d2,bb
 
UTF-8         AL32UTF8             Typ=1 Len=3 CharacterSet=AL32U
                                     TF8: e4,b8,80
 
UTF-8         ZHS16GBK        涓€    Typ=1 Len=6 CharacterSet=AL32U
                                     TF8: e6,b6,93,e2,82,ac
对上面4条记录进行分析可以看出,当NSL_LANG设置的字符集和客户端相符时,在数据库中插入的字符是正确的,否则是乱码。在客户端和字符集和数据库的字符集不一致时,Oracle会自动完成字符的转换,例如第一条记录客户端的GBK字符集被自动转换成了UTF-8的编码。第二条记录Oracle根据NLS_LANG的设置误认为客户端是UTF-8的字符集,就没有进行转换,结果把“一”的GBK编码“d2bb”原样插入到了数据库中。因此这条记录如果原样输出到GBK的客户端会正确的显示出汉字“一”,例如:
C:\>set NLS_LANG=.AL32UTF8
……
SQL> select CH from test where client_char='GBK' and nls_lang='AL32UTF8';
 
CH
-------------------------
在这个例子中,因为NLS_LANG中设置的客户端字符集和数据库字符集一样,因此Oracle不经过转换就在GBK的客户端显示出了正确的GBK编码的汉字。这样的错误有时会造成用户和DBA之间的误会,一方看到的是正确的汉字,另一方看到的却是乱码,因此有时眼睛看到的不一定可信,关键还是要用dump函数看数据库中保存的汉字内码。
 
 
 
Oracle的传输表空间功能不支持不同字符集的数据库之间进行表空间的传输。
Oracle的数据泵采用源数据库的字符集来保存数据,在导出的数据文件中以XML的格式保存字符集的信息。
$ strings scott.dmp |less
......
<?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>AL32UTF8</CHARSET><NCHARSET>AL16UTF16</NCHARSET><DBTIMEZONE>+00:00</DBTIMEZONE>
......
 
数据泵在进行导入时,如果源数据库的字符集与目标数据库的字符集相同或是它的子集,则不进行字符集的转换,否则会自动进行字符集的转换。
SQL*Loader可将外部文件中的数据加载到Oracle 数据库表中。此时环境变量NLS_LANG的设置和前面的SQLPLUS有所不同,此时的NLS_LANG不是设置成客户端的字符集,而是被加载文件的字符集。
例如,我们先准备一个被加载的文件test.dat,把它加载到我们前面建的test表中。在UTF-8的客户端查看一下文件。
$ cat test.dat
UTF8,一
GBK,?
$ cat test.dat| od -t x1
0000000 55 54 46 38 2c e4 b8 80 0a 47 42 4b 2c d2 bb 0a
0000020
$
从上面我们可以看出,这个文件的第一行的汉字“一”是utf-8的字符集,第二行是gbk的字符集,当然,实际工作中是不会有这样的文件的。
我们先把NLS_LANG设置成AL32UTF8后,进行加载。
$ cat test.ctl
load data
infile 'test.dat'
into table test truncate
fields terminated by ','
(CLIENT_CHAR,CH)
$ export NLS_LANG=.AL32UTF8
$ sqlldr \'/ as sysdba\' control=test 
 
SQL*Loader: Release 10.2.0.1.0 - Production on Tue Jan 26 09:34:45 2010
 
Copyright (c) 1982, 2005, Oracle. All rights reserved.
 
 
Load completed - logical record count 2.
 
SQL> col dump_ch for a30
SQL> select client_char,ch,dump(ch,1016) dump_ch from test;
 
CLIENT_CHAR   CH     DUMP_CH
------------- ------ ------------------------------
UTF8               Typ=1 Len=3 CharacterSet=AL32U
                     TF8: e4,b8,80
 
GBK           ?      Typ=1 Len=2 CharacterSet=AL32U
                     TF8: d2,bb
 
 
经过检查发现由于NLS_LANG中指定字符集与数据库的字符集相同,都是AL32UTF8,因此字符没有发生转换。
再把NLS_LANG设置成ZHS16GBK后,进行加载。
$ export NLS_LANG=.ZHS16GBK
$ sqlldr \'/ as sysdba\' control=test
 
SQL*Loader: Release 10.2.0.1.0 - Production on Tue Jan 26 09:39:42 2010
 
Copyright (c) 1982, 2005, Oracle. All rights reserved.
 
 
Load completed - logical record count 2.
$
 
SQL> select client_char,ch,dump(ch,1016) dump_ch from test;
 
CLIENT_CHAR   CH     DUMP_CH
------------- ------ ------------------------------
UTF8          涓€    Typ=1 Len=6 CharacterSet=AL32U
                     TF8: e6,b6,93,e2,82,ac
 
GBK                Typ=1 Len=3 CharacterSet=AL32U
                     TF8: e4,b8,80
 
此时GBK的字符集实正确转换成UTF-8,而原来的UTF-8的字符却成了乱码。除了可以通过NLS_LANG来指定文件的字符集外,也可以在SQL*Loader的控制文件中指定characterset,此时以控制文件中的设定为准,如:
$ cat test.ctl
load data
characterset UTF8
......
从上面的例子中可以看出,要准确判断被加载文件的字符集才能保证字符转换的正确。
Oracle提供了一个工具LCSSCAN(Language and Character Set File Scanner)用于判断文件所适用的字符集。
$ lcsscan --help
 
 
 Language and Character Set File Scanner v2.1
 
 (c) Copyright 2003, 2004 Oracle Corporation. All rights reserved.
 
 
 You can control how LCSSCAN runs by entering the LCSSCAN command
 followed by the required parameters. To specify parameters, you use
 keywords:
 
   Example: LCSSCAN RESULTS=2 END=1000 FORMAT=HTML FILE=index.html
 
 Keyword    Description (Default)
 --------------------------------------------------------------------
 RESULTS    number of language and character set pairs to return (1)
 BEGIN      beginning byte offset of file (1)
 END        ending byte offset of file (end of file)
 FORMAT     file format TEXT, HTML or AUTO detect (TEXT)
 FILE       name of input file
 HELP       show help screen (this screen)
 
$
FILE就是要扫描的文件,BEGIN和END则指定只扫描文件的部分内容。RESULTS指明要返回几组可用的结果,因为一个文件可能兼容于多种字符集。FORMAT指明文件的类型。例如,把新浪的首页保存后,用这个工具扫描:
C:\>lcsscan format=html file=sina.htm
 
 
 Language and Character Set File Scanner v2.1
 
 (c) Copyright 2003, 2004 Oracle Corporation. All rights reserved.
 
 
sina.htm:       SIMPLIFIED CHINESE ZHS16GBK;
 
Oracle数据库中对字符集处理包括3个部分:客户端的字符集、数据库的字符集和这两者之间可能发生的转换。对客户端和数据库的字符集的判断不能仅仅用眼睛看,而应查看实际保存的汉字内码。数据库和客户端之间的字符集转换通常依据的是环境变量NLS_LANG的设置。