Oracle数据库多语言文字存储解决方案
一、关于字符集
字符集(也称字元集,Character Set)就是字符编码表(codepage),一个字符不论英文、中文、韩文等在计算机系统内存或硬盘中通过二进制的字节(Byte)保存,这个二进制的编码就是字符编码(也称内码),字符集就是字符与内码的对应(映射)表。
因为多国语言的原因,就出现了根据本国语言制作的字符集。如使用最广泛的ASCII编码,由美国国家标准局(即ANSI)制定,适用于所有拉丁、英文字符。中国大陆使用GB2312,GBK,GB18030等字符集,这些字符集包含所有汉字字符的内码,其中GBK,GB18030称为大字符集,对繁体中文也进行了编码。香港、台湾、澳门地区使用Big5编码,Big5收录了繁体中文(有些繁体与中国大陆繁体字符有差异)的编码,不包含简体中文的字符编码。韩文使用euc-kr的字符集,韩文中也有很多汉字,所以字符集包括很多汉字字符的编码。其他如日文、俄文等都有自己国家制定的字符集,用来保证计算机系统能正确显示本国的语言文字。不同语言的字符集不具有通用性,ASCII字符集没有制定中文字符的编码,GB2312没有制定韩文字符的编码,Big5没有制定简体中文字符的编码,针对这种不兼容性,官方发布了Unicode(进一步优化的UTF7,UTF8,UTF16等)字符集,对每一种语言的每个字符制定了统一且唯一的内码,满足跨语言、跨平台的字符解码和转换处理。
字符集编码(16进制)示例:
字符/字符集 |
GBK |
Euc-kr |
UTF8 |
UTF16 |
物流 |
ce-ef ,c1-f7 |
da-aa,d7-b5 |
e7-89-a9,e6-b5-81 |
72-69,6d-41 |
?? |
无 |
bb-ef ,bc-ba |
ec-82-bc,ec-84-b1 |
c0-bc,c1-31 |
注:
1) 字符“??”在韩文字符集Euc-kr中的编码是bb-ef-bc-ba,在GBK字符集中是没有“??”这两个字符的,也就是说bb-ef-bc-ba在Euc-kr与GBK编码对照表中是没有记录的,如果你硬是要GBK字符集来对“??”作出解释(解码),那GBK就用字符“?”(因韩文字符是两个字节,所以使用全角?)代替,全角?的编码在GBK中是a3-bf。
2) 汉字“物流”字符在Euc-kr中的编码是da-aa,d7-b5,这说明韩文字符集中包含了部分汉字的编码,当然这个编码与GBK字符集中“物流”两个字符的编码(ce-ef ,c1-f7)是不同的,用GBK去解释韩文字符集中“物流”两个字符,显示的结果肯定不是“物流”两个字符。同样的,在GBK中很多繁体中文字符的编码与BIG5中相同繁体中文的字符编码也是不同的,例如你在简体中文环境开发应用程序时,窗体控件使用繁体中文表示,但是在繁体OS运行应用程序,控件上的繁体中文变成了乱码或?,原因就是不同字符集同样字符的编码是不同的,解决这个问题的方法就是将应用程序使用unicode编码保存,告诉操作系统使用unicode字符集对你的应用程序中的字符进行解码。
Windows操作系统(OS)的字符集:不同语言的OS的默认字符集是不一样的。英文OS使用ASCII字符集作为系统的字符集,简体使用GB2312,繁体使用Big5(在VB.NET中,可用System.Text.Encoding.Default.EncodingName检查OS的字符集)。Windows系统本身对系统默认的字符集有很好的支持,但是安装在OS上的应用程序却不一定这样。例如,在一个简体中文操作系统上安装了韩文版的某个应用程序,这个应用程序在开发时使用的是euc-kr字符集编码。因为OS默认的处理非Unicode程序的字符集是GB2312,在 GB2312字符集并未对任何韩文字符进行编码,在GB2312内找不到任何一个韩文字符的内码,找不到只能以“?”代替这个字符,对应的编码变成了“?”的编码,例如:“??”这两个韩文字符在程序运行时显示的是“?”。解决这个问题,有三种方法:1,该韩文应用程序使用Unicode编码保存。在简体环境运行时,OS使用Unicode字符集解码,只要系统安装韩文字体,就可正常显示韩文;2,将OS处理非Unicode程序使用的字符集改为euc-kr,支持韩文应用程序的解码(在control panel->Regional and Language Options 修改);3,安装微软的AppLocale工具,指定该韩文应用程序运行时使用euc-kr的字符集。
一、Oracle字符集
Oracle字符集包括两部分。一部分是Server端数据库运行实例(instance)的字符集,一部分是Oracle客户端Client的字符集。
1, 数据库实例的字符集(以Oracle 10g为例)
在安装Oracle数据库过程中,可以选择数据库字符集。默认的是OS系统的字符集,如简体中文系统是GB2312,繁体系统是BIG5。Oracle对于简体系统的字符集使用ZHS16GBK,GBK是GB2312的超集,Oracle不识别GB2312,只认ZHS16GBK。此外,选择Unicode作为数据库字符集,所有存储数据都将以Unicode编码存储在数据库中,不论字段类型是Number,varchar2,DateTime等。或者也可以从字符集列表中选择其他字符集。注意到有个国家字符集的选项,而且国家字符集的选择只有unicode,这是为当数据库字符集为非Unicode时将数据存为Unicode编码时用到。比如:数据库字符集为ZHS16GBK,数据库表mer_categ的一个字段为S_merc_name,数据类型为varchar2,存入中文字符“物流”,“物流”这两个字符使用GBK字符集编码存储。通过“select dump(s_merc_name,16) from mer_categ”查询该字段在数据库中的内码得到结果是:ce,ef ,c1,f7,这与我们在前面看到的示例表中这两个字的内码是一致的。现在,把S_merc_name这个字段类型改为nvarchar2,意味着这个字段数据的存储将使用National Charset(国家字符集)——AL16UTF16的编码存储。同样写入“物流”两个中文字符,再次查询该数据在数据库中的内码结果是:e7,89,a9e6,b5,81,这就是我们为什么使用nvarchar2作为字段类型的原因。回过头来看,如果数据库字符集选择了Unicode,那表字段中使用nvarchar2已经没什么意义了,因为所有字段类型的数据都是使用Unicdoe编码来保存的。
查询数据库字符集的sql指令是:
“select * from v$nls_parameters”
得出结果:
NLS_LANGUAGE |
SIMPLIFIED CHINESE |
NLS_TERRITORY |
CHINA |
NLS_CURRENCY |
锟 |
NLS_ISO_CURRENCY |
CHINA |
NLS_NUMERIC_CHARACTERS |
., |
NLS_CALENDAR |
GREGORIAN |
NLS_DATE_FORMAT |
DD-MON-RR |
NLS_DATE_LANGUAGE |
SIMPLIFIED CHINESE |
NLS_CHARACTERSET |
AL32UTF8 |
NLS_SORT |
BINARY |
NLS_TIME_FORMAT |
HH.MI.SSXFF AM |
NLS_TIMESTAMP_FORMAT |
DD-MON-RR HH.MI.SSXFF AM |
NLS_TIME_TZ_FORMAT |
HH.MI.SSXFF AM TZR |
NLS_TIMESTAMP_TZ_FORMAT |
DD-MON-RR HH.MI.SSXFF AM TZR |
NLS_DUAL_CURRENCY |
锟 |
NLS_NCHAR_CHARACTERSET |
UTF8 |
NLS_COMP |
BINARY |
NLS_LENGTH_SEMANTICS |
BYTE |
NLS_NCHAR_CONV_EXCP |
FALSE |
其中NLS_CHARACTERSET 是数据库的字符集;NLS_NCHAR_CHARACTERSET是国家字符集。
数据库的字符集在建立数据库的时候创建。目前也有方法转换字符集,但两个字符集的转换还是存在风险,造成数据丢失或错误,不建议使用。
2, Oracle Client字符集(NLS_LANG)
在访问Oracle的客户端安装Oracle Client过程中并没有选项选择Oracle Client的字符集,安装完毕后在注册表HKLOCAL_MACHINE\SOFTWARE\ORACLE\KEY_ORACLECLENT_HOME1\可以找到NLS_LANG键,值为当前OS的字符集。如简体系统为:ZHS16GBK,繁体系统为:MSWIN950。可见,Oracle Client(以下简称NLS_LANG)在安装过程中选择了OS的字符集作为默认的NLS_LANG字符集。
设定NLS_LANG有三种方法:
a) 修改注册表。将HKLOCAL_MACHINE\SOFTWARE\ORACLE\KEY_ORACLECLENT_HOME1\下NLS_LANG键值改为你要设定的字符集,如将SIMPLIFIED CHINESE_CHINA.ZHS16GBK改为:SIMPLIFIED CHINESE_CHINA.AL32UTF8。但这种做法似乎无效,即使重新启动机器后,也没有生效,NLS_LANG仍使用当初安装时的OS字符集。
b) 设定环境变量。在My Computer->Properties->Advanced->Environment Variables->System Variables 新增环境变量设置,如:Variable name=NLS_LANG,Variable Value= SIMPLIFIED CHINESE_CHINA.AL32UTF8。这样NLS_LANG字符集为UTF8,这个NLS_LANG优先序高于注册表中的NLS。注意:环境变量设在系统变量中(System Vairables),而不是用户变量(User Vairables)。
c) 在应用程序运行的Process Session中设定。在程序运行之前,先通过set NLS_LANG=进程Session的字符集。例如:
Echo %nls_lang%,在这个session中,已经设定NLS_LANG字符集为ZHS16GBK。同样,你也可以新开一个CMD窗口,设定另一种NLS_LANG字符集。这种在session中设定NLS_LANG的优先序高于系统环境变量NLS_LANG。注册表NLS_LANG、系统环境变量NLS_LANG、Session NLS_LANG的优先序是:Session NLS_LANG > 系统环境变量NLS_LANG > 注册表NLS_LANG。
三、Oracle数据库如何存储多国文字
首先,先看一个有趣的问题。我们要保存的文字哪里来?一种方法是在屏幕上输入,输入中文时,我们打开自己习惯的输入法,在应用程序给你提供的输入框内输入文字;还有一种方法是复制粘贴的方法,将文字从网页、文档中拷贝后再粘贴到输入框中,那问题是:输入框中的文字使用的字符集是什么?有人说,是客户端操作系统的默认字符集。好,我的操作系统是简体中文,默认系统字符集是GB2312,在输入框中输入韩文字符“??”,如果这两个韩文字符是用GB2312字符集解码的,输入框内应该显示两个“?”,因为GB2312字符集内没有韩文字符只能用“?”代替。但是现在这两个韩文字符在输入框内正常的显示,说明这两个韩文的字符集是支持韩文字符的字符集,是韩文字符集“euc-kr”,还是Unicode字符集?肉眼看不出来,也许查看一下内存中输入框中文字的编码能找到答案,喜欢钻研的人可以去看一看。表面来判断,可能是根据输入法使用的字符集,输入法使用什么样的字符集,输入的文字就是用的什么样的字符集。从网页上复制粘贴过来的文字,应该跟网页的字符集是一致的。其实,我们不关心多国文字背后的字符集是用的本国字符集还是unicode字符集,我们关心的是多国文字如何在数据库中被正确地存取。
以oracle 10g R2 10.2.0.3 作为测试数据库版本,在数据库建立一张表:
表名称:mer_categ,商品类别表
字段 |
数据类型 |
长度 |
说明 |
S_merc_id |
Varchar2 |
20 |
分类编号 |
S_merc_name |
Varchar2 |
50 |
分类名称 |
现在要新增一个分类编号为01,分类名称为“??”的记录。即使SQL的初学者也会写出:
Insert into mer_catag values (‘01’,’??’);
这条SQL语句没有错,但是能不能把“??”正确的写入数据库,仅仅靠一条SQL语句是不够的,在分析各种测试环境之前,我们看一段官方关于SQL语句中字符串字符编码的描述:
“Being part of a SQL or PL/SQL statement, the text of any literal, with
or without the prefix N
, is
encoded in the same character set as the rest of the
statement. On the client side, the statement is in the client character set,
which is determined by the client character set defined in NLS_LANG
,
or specified in theOCIEnvNlsCreate()
call, or
predefined as UTF -16 in JDBC. On the server side the statement is in the
database character set.”
这段话告诉我们:SQL语句提交到Server之前,SQL语句中的字符串部分(不管前面有没有N’)作为语句的一部分将被用NLS_LANG定义的字符集进行编码。提交到Server端后,再被数据库字符集编码。可以推出:如果数据库字符集与客户端NLS_LANG字符集一致,在Server端就没有必要再一次编码了。
在安装oracle数据库时如果一直选择“下一步”,数据库字符集将默认使用操作系统字符集。如在简体OS下,安装Oracle Server,创建Oracle实例后,实例的数据库字符集默认是ZHS16GBK;安装客户端后,NLS_LANG默认是ZHS16GBK字符集。GBK是汉字大字符集,处理汉字简繁体都可以,所以平时使用类似:
Insert into mer_catag values (‘01’,’中国’);
Insert into mer_catag values (‘01’,’中國’);
都是正常的。但是当处理多国文字时,就不灵了。如:
Insert into mer_catag values (‘01’,’??’);
根据前面引用的官方描述,这条SQL语句无论是字符串部分还是其他部分,将先被NLS_LANG定义的字符集转换,“??”将用GBK字符集转换,前面讲过,“??”转换后的字符是“??”(两个全角问号)。SQL语句变成了:
Insert into mer_catag values (‘01’,’??’);
在server端,因数据库字符集与NLS_LANG一致,SQL语句不再进行字符集转换,执行SQL后,数据库存储了两个全角“?”。用select dump(s_merc_name,16) from mer_categ验证一下,得到结果是:Typ=1 Len=4: a3,bf,a3,bf。“a3,bf”就是全角”?”的GBK编码。两个韩文字符被当作”?”存在了数据库中,查询出的当然也是”?”。所以,不对oracle字符集进行配置,不能正确处理多国文字的存储。
客户端的NLS能实现对多国文字进行正确字符集转换的话,毫无疑问是选择Unicode字符集。用set NLS_LANG =SIMPLE CHINESE.AL32UTF8可以修改客户端的NLS为UTF8字符集,还是以
Insert into mer_catag values (‘01’,’??’);
为例,在客户端,这条SQL语句使用UTF8字符集做了编码转换,韩文字符“??”的编码转为:“ec-82-bc,ec-84-b1”(UTF8编码)。下一步,关键看Server端的数据库字符集了,如果数据库字符集仍为GBK,也就是说,“?? ”要作UTF8->GBK的转码,能转成功吗?显然不可以,连个韩文字符又被全角“?”代替了。在server端,SQL语句被转成了:
Insert into mer_catag values (‘01’,’??’);
数据库又存了两个全角“?”。怎么办?Server端能不能不作字符集转换?前面提到,如果数据库字符集与客户端NLS_LANG字符集一致,在Server端就没有必要再一次编码了。好,那就在Server端将数据库字符集设为UTF8,韩文字符“??”就以UTF8的字符编码存到数据库里了。验证一下,select dump(s_merc_name,16) from mer_categ,结果是:Typ=1 Len=6: ec,82,bc,ec,84,b1。这个结果与前面看到的UTF8编码是一致的。所以,当数据库字符集与NLS_LANG(客户端)都设为UTF8字符集时,可以解决多国文字正确存储的问题。
似乎将数据库字符集与NLS_LANG都设为UTF8的做法未得到广泛应用,原因是大多数的应用不存在使用多国语言的问题?没做过相关调查。除此之外,使用UTF8存储数据占用更多的空间是事实存在的,一个汉字字符UTF8使用三个字节存储,而在GBK中使用两个字节,用GBK作为数据库字符集存储汉字更经济实惠些。最后,任何存入数据库的数据都先经过UTF8的转码再存储,必然带来效能的损失。那能不能区别对待存储的数据呢?例如只针对存在多国字符的数据使用Unicode字符集存储呢?这就是国家字符集(National Chartset)存在的原因。国家字符集是那些存放在NCHAR,NVARCHAR2,NCLOB字段中的数据的Unicode字符编码集,Oracle 10g使用AL16UTF16字符集作为默认的数据库国家字符集。如何将多国文字存储到NVARCHAR2这样的字段中去?