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 UTF8UTF16
72-69,6d-41
物流ce-ef ,c1-f7da-aa,d7-b5e7-89-a9,e6-b5-
81
⽆bb-ef ,bc-ba ec-82-bc,ec-84-
c0-bc,c1-31
b1
注:
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 Varchar220分类编号
S_merc_name Varchar250分类名称
现在要新增⼀个分类编号为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 enco
ded 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 the OCIEnvNlsCreate() 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的转码再存储,必然带来效能的损ascii文字是啥
失。那能不能区别对待存储的数据呢?例如只针对存在多国字符的数据使⽤Unicode字符集存储呢?这就是国家字符集(National Chartset)存在的原因。国家字符集是那些存放在NCHAR,NVARCHAR2,NCLOB字段中的数据的Unicode字符编码集,Oracle 10g使⽤AL16UTF16字符集作为默认的数据库国家字符集。如何将多国⽂字存储到NVARCHAR2这样的字段中去?
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论