最近碰到Oracle乱码问题,刚开始甚是头疼,以前在合肥出差的时候,这种问题也碰到过,当时直接抛给了“乌压压一片”(一个搞数据的同事儿),这次没办法躲过,只好硬着头皮上。虽然我这次碰到的是Oracle乱码问题中的一个,但是我决定将这个乱码问题整理清楚(不整清楚,就觉得身边有个定时炸弹,怕下次整数据库的时候会突然又爆炸)。
一、字符、字节和编码(熟悉的人或者急于解决问题的人,直接跳过这一节。备注:此节内容非原创,查看原创请连接: http://www.regexlab.com/zh/encoding.htm)
1.1 字符与编码的发展
从计算机对多国语言的支持角度看,大致可以分为三个阶段:
系统内码 | 说明 | 系统 | |
阶段一 | ASCII | 计算机刚开始只支持英语,其它语言不能够在计算机上存储和显示。 | 英文 DOS |
阶段二 |
ANSI编码 (本地化) |
为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 '中' 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。 不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。 不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。 |
中文 DOS,中文 Windows 95/98,日文 Windows 95/98 |
阶段三 |
UNICODE (国际化) |
为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。 | Windows NT/2000/XP,Linux,Java |
字符串在内存中的存放方法:
在 ASCII 阶段,单字节字符串使用一个字节存放一个字符(SBCS)。比如,"Bob123" 在内存中为:
42 | 6F | 62 | 31 | 32 | 33 | 00 |
B | o | b | 1 | 2 | 3 | \0 |
在使用 ANSI 编码支持多种语言阶段,每个字符使用一个字节或多个字节来表示(MBCS),因此,这种方式存放的字符也被称作多字节字符。比如,"中文123" 在中文 Windows 95 内存中为7个字节,每个汉字占2个字节,每个英文和数字字符占1个字节:
D6 | D0 | CE | C4 | 31 | 32 | 33 | 00 |
中 | 文 | 1 | 2 | 3 | \0 |
在 UNICODE 被采用之后,计算机存放字符串时,改为存放每个字符在 UNICODE 字符集中的序号。目前计算机一般使用 2 个字节(16 位)来存放一个序号(DBCS),因此,这种方式存放的字符也被称作宽字节字符。比如,字符串 "中文123" 在 Windows 2000 下,内存中实际存放的是 5 个序号:
2D | 4E | 87 | 65 | 31 | 00 | 32 | 00 | 33 | 00 | 00 | 00 | ← 在 x86 CPU 中,低字节在前 |
中 | 文 | 1 | 2 | 3 | \0 |
一共占 10 个字节。
1.2 字符,字节,字符串
理解编码的关键,是要把字符的概念和字节的概念理解准确。这两个概念容易混淆,我们在此做一下区分:
概念描述 | 举例 | |
字符 | 人们使用的记号,抽象意义上的一个符号。 | '1', '中', 'a', '$', '¥', …… |
字节 | 计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存储空间。 | 0x01, 0x45, 0xFA, …… |
ANSI 字符串 |
在内存中,如果“字符”是以 ANSI 编码形式存在的,一个字符可能使用一个字节或多个字节来表示,那么我们称这种字符串为 ANSI 字符串或者多字节字符串。 | "中文123" (占7字节) |
UNICODE 字符串 |
在内存中,如果“字符”是以在 UNICODE 中的序号存在的,那么我们称这种字符串为UNICODE 字符串或者宽字节字符串。 | L"中文123" (占10字节) |
由于不同 ANSI 编码所规定的标准是不相同的,因此,对于一个给定的多字节字符串,我们必须知道它采用的是哪一种编码规则,才能够知道它包含了哪些“字符”。而对于 UNICODE 字符串来说,不管在什么环境下,它所代表的“字符”内容总是不变的。
1.3 字符集与编码
各个国家和地区所制定的不同 ANSI 编码标准中,都只规定了各自语言所需的“字符”。比如:汉字标准(GB2312)中没有规定韩国语字符怎样存储。这些 ANSI 编码标准所规定的内容包含两层含义:
1) . 使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫做“字符集”。
2) .规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码”。
各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”一般都是同时制定的。因此,平常我们所说的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。
“UNICODE 字符集”包含了各种语言中使用到的所有“字符”。用来给 UNICODE 字符集编码的标准有很多种,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。
1.4 常用的编码简介
简单介绍一下常用的编码规则,为后边的章节做一个准备。在这里,我们根据编码规则的特点,把所有的编码分成三类:
分类 | 编码标准 | 说明 |
单字节字符编码 | ISO-8859-1 |
最简单的编码规则,每一个字节直接作为一个 UNICODE 字符。比如,[0xD6, 0xD0] 这两个字节,通过 iso-8859-1 转化为字符串时,将直接得到 [0x00D6, 0x00D0] 两个 UNICODE 字符,即 "ÖÐ"。 反之,将 UNICODE 字符串通过 iso-8859-1 转化为字节串时,只能正常转化 0~255 范围的字符。 |
ANSI 编码 |
GB2312, BIG5, Shift_JIS, ISO-8859-2 …… |
把 UNICODE 字符串通过 ANSI 编码转化为“字节串”时,根据各自编码的规定,一个 UNICODE 字符可能转化成一个字节或多个字节。 反之,将字节串转化成字符串时,也可能多个字节转化成一个字符。比如,[0xD6, 0xD0] 这两个字节,通过 GB2312 转化为字符串时,将得到 [0x4E2D] 一个字符,即 '中' 字。 “ANSI 编码”的特点: 1. 这些“ANSI 编码标准”都只能处理各自语言范围之内的 UNICODE 字符。 2. “UNICODE 字符”与“转换出来的字节”之间的关系是人为规定的。 |
UNICODE 编码 |
UTF-8, UTF-16, UnicodeBig …… |
与“ANSI 编码”类似的,把字符串通过 UNICODE 编码转化成“字节串”时,一个 UNICODE 字符可能转化成一个字节或多个字节。 与“ANSI 编码”不同的是: 1. 这些“UNICODE 编码”能够处理所有的 UNICODE 字符。 2. “UNICODE 字符”与“转换出来的字节”之间是可以通过计算得到的。 |
我们实际上没有必要去深究每一种编码具体把某一个字符编码成了哪几个字节,我们只需要知道“编码”的概念就是把“字符”转化成“字节”就可以了。对于“UNICODE 编码”,由于它们是可以通过计算得到的,因此,在特殊的场合,我们可以去了解某一种“UNICODE 编码”是怎样的规则。
1.5 几种误解,以及乱码产生的原因和解决办法
对编码的误解 | |
误解一 |
在将“字节串”转化成“UNICODE 字符串”时,比如在读取文本文件时,或者通过网络传输文本时,容易将“字节串”简单地作为单字节字符串,采用每“一个字节”就是“一个字符”的方法进行转化。 而实际上,在非英文的环境中,应该将“字节串”作为 ANSI 字符串,采用适当的编码来得到 UNICODE 字符串,有可能“多个字节”才能得到“一个字符”。通常,一直在英文环境下做开发的程序员们,容易有这种误解。 |
误解二 |
在 DOS,Windows 98 等非 UNICODE 环境下,字符串都是以 ANSI 编码的字节形式存在的。这种以字节形式存在的字符串,必须知道是哪种编码才能被正确地使用。这使我们形成了一个惯性思维:“字符串的编码”。 当 UNICODE 被支持后,Java 中的 String 是以字符的“序号”来存储的,不是以“某种编码的字节”来存储的,因此已经不存在“字符串的编码”这个概念了。只有在“字符串”与“字节串”转化时,或者,将一个“字节串”当成一个 ANSI 字符串时,才有编码的概念。不少的人都有这个误解。 |
第一种误解,往往是导致乱码产生的原因。第二种误解,往往导致本来容易纠正的乱码问题变得更复杂。
在这里,我们可以看到,其中所讲的“误解一”,即采用每“一个字节”就是“一个字符”的转化方法,实际上也就等同于采用 iso-8859-1 进行转化。因此,我们常常使用 bytes = string.getBytes("iso-8859-1") 来进行逆向操作,得到原始的“字节串”。然后再使用正确的 ANSI 编码,比如 string = new String(bytes, "GB2312"),来得到正确的“UNICODE 字符串”。
二、Oracle中文乱码解决正文
2.1 操作环境
操作系统:win7 64bit
数据库:Oracle 10.2.0.4.0 64bit
PLSQL版本号:9.0.6
先说说小生遇到的两个乱码问题吧,问题一:某xxxx.sql文件,里面都是insert语句,并且文本编辑器打开文件查看,里面待插入的中文数据显示正常,但是通过命令行,使用“@xxxx.sql”导入数据库后,发现数据库中的中文数据都是“?????”这种形式;问题二:在确保导入数据库中的中文数据正常的前提下,用plsql查看数据库中的数据,plsql中文显示为“??????”。
2.2 解决方案如下
针对问题一:
修改注册表:HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\KEY_OraDb10g_home1\NLS_LANG,其值修改成:SIMPLIFIED CHINESE_CHINA.ZHS16GBK
针对问题二:
修改系统环境变量NLS_LANG的值(如果没有就新建一个):SIMPLIFIED CHINESE_CHINA.ZHS16GBK
三、解决数据库乱码原理特辑内容
3.1 前言
在解决数据库乱码问题中,涉及到三个方面的字符集:1、oracel server端的字符集;2、oracle client端的字符集;3、dmp文件的字符集(只有在需要往数据库里面导入dmp文件的时候会涉及到这点)。
3.2 知识储备
查看Oracle客户端字符集:
SELECT * FROM V$NLS_PARAMETERS;--返回当前用户环境中设置的字符集
SELECT USERENV('language') FROM DUAL;--返回当前会话使用的字符集
特别说明:客户端的字符集要求与服务器一致,才能正确显示数据库的非Ascii字符,如果多个设置存在的时候,NLS作用优先级别:Sql function > alter session > 环境变量>注册表>参数文件> 数据库默认参数
查看Oracle服务端字符集:
SELECT * FROM NLS_DATABASE_PARAMETERS;--返回Oracle server端的字符集,来源于props$,是表示数据库的字符集
查看dmp文件的字符集:
用oracle的exp工具导出的dmp文件也包含了字符集信息,dmp文件的第2和第3个字节记录了dmp文件的字符集。如果dmp文件不大,比如只有几M或几十M,可以用UltraEdit打开(16进制方式),看第2第3个字节的内容,如0354,然后用以下SQL查出它对应的字符集:
“select nls_charset_name(to_number('0354','xxxx')) from dual;”
如果dmp文件很大,比如有2G以上(这也是最常见的情况),用文本编辑器打开很慢或者完全打不开,可以用以下命令(在unix主机上):
“cat exp.dmp |od -x|head -1|awk '{print $2 $3}'|cut -c 3-6”
然后用上述SQL也可以得到它对应的字符集
NLS_LANG参数格式:
Language_Territory.Clientcharacterset
其中,Language显示oracle消息,校验,日期命名,Territory指定默认日期、数字、货币等格式Clientcharacterset指定客户端将使用的字符集。比如NLS_LANG=AMERICAN_AMERICA.US7ASCII。AMERICAN是语言,AMERICA是地区,US7ASCII是客户端字符集。在解决客户端和服务端编码不一致问题导致的乱码时,Language和Territory可以不一致,但是Clientcharacterset必须一致。
其他数据库字符集查询相关语句:
实例字符集环境
SELECT * FROM NLS_INSTANCE_PARAMETERS;--显示由参数文件init.ora定义的参数
主要涉及NLS_LANGUAGE、NLS_TERRITORY的值. NLS_INSTANCE_PARAMETERS其来源于v$parameter,注意:网上很多资料都说"NLS_INSTANCE_PARAMETERS 表示客户端的字符集的设置,可以是参数文件,环境变量或者是注册表",而且网上都人人亦云。记住它是表示实例的字符集环境
数据库可用字符集参数设置
SELECT * FROM V$NLS_VALID_VALUES
会话字符集环境
SELECT * FROM NLS_SESSION_PARAMETERS;
它来源于v$nls_parameters,表示会话自己的设置,可能是会话的环境变量或者是ALTER SESSION完成,如果会话没有特殊的设置,将与 V$NLS_PARAMETERS一致
Oracle数据传递编码/转码过程:(原文链接:http://blog.163.com/jiankun_liu/blog/static/1863927762013698175289/)
首先,要说清楚Oracle字符集的相关问题,则要先理清数据库运行过程中的架构以及在这个架构中的字符集设置及这些设置之间的关联关系,先画一张图看一下:
在这个图中,为了说明问题,我们将服务器与客户端分开,客户端用应用程序比如sqlplus或者PL/SQL与服务端相连。
服务端有两个字符集:服务端操作系统字符集(4)、服务端数据库字符集(1);
客户端有一个字符集:客户端操作系统字符集(2);
客户端有一个参数:操作系统参数NLS_LANG(1)。
这三个字符集与一个参数中,有一个字符集对整个架构的运行没有影响,它就是服务端操作系统字符集(4),所以这个字符集将不再出现在我们的讨论过程中。
为什么这个服务端操作系统字符集没有用呢?这是因为Oracle在存取字符时与客户端进行字符集确认与转码的过程是由Oracle数据库自身完成的,不需要经过Oracle 数据库所在的服务器的帮助。具体的是怎么回事用以下例子说明一下。
比如在Oracle数据库中有一个表,用如下语句创建:
create table test(name varchar2(10));
为了说明问题假定有这样的一个环境:服务器端Oracle数据库的字符集是UTF8,客户端操作系统字符集是ZHS16GBK,客户端NLS_LANG参数设置为ZHS16GBK。那么从客户端应用程序(比如sqlplus)发出这样一条命令:
insert into test (name) values('中国');
首先,这里有一个字符串“中国”,客户端操作系统用ZHS16GBK对它进行编码,比如编成“167219”,并把它交给sqlplus程序,然后把它发送给Oracle数据库。接着,Oracle数据库收到一串编码“167219”,不是直接往数据库里一扔就完事的,它要问客户端操作系统:“请问你给我的这串代码是用什么格式编码的啊?”客户端操作系统怎么回答?它会这么回答:“编码格式请参照参数NLS_LANG”。Oracle数据库一看,NLS_LANG='ZHS16GBK',这个编码格式与Oracle数据库自身的编码格式“UTF8”不一样,然后就是Oracle数据库发挥自己专长的地方了,为什么呢?因为Oracle数据库有它自己的编码表,而且不是一张而是好多张编码表,它可以根据编码表对编码进行翻译和转码。这就好比Oracle数据库是一个翻译,它会好几国语言,牛人一个。像上面的这个情况,Oracle会把“167219”这串代码拿过来,根据参数NLS_LANG查ZHS16GBK编码表,找到对应这串代码的字符“中国”,然后再到UTF8编码表中查“中国”对应的编码,比如查到的结果是“3224678”。
最后,将转码之后的编码“3224678”存放到Oracle数据库中去。
为了进一步说明问题,我们再执行一条语句:
select name from test;
首先,Oracle数据库会从数据库中取出一串代码“3224678”。
接着,Oracle数据库不是直接把这串代码交给sqlplus程序,它会多问一句:“代码串我是取出来了,它是UTF8编码格式的,请问sqlplus,你希望要什么编码格式的?”,sqlplus仍然会很爽快地告诉Oracle数据库:“编码格式请继续参照参数NLS_LANG”。Oracle数据库一看,ZHS16GBK跟UTF8又不一样,所以先查UTF8编码表,找到编码“3224678”对应的字符“中国”,再查ZHS16GBK编码表,找到“中国”对应的编码“167219”,然后就是把最后得到的这串编码“167219”交给sqlplus程序。
最后,sqlplus直接把得到的这串编码扔给客户端操作系统,而操作系统只有ZHS16GBK编码表,它不会问得到的这串编码是什么格式的,只会直接到ZHS16GBK编码表中去查“167219”对应的字符是什么,并把它交给应用程序显示出来。这个显示的结果是“中国”。
以上就是一个完整的从客户端编码并经过Oracle数据库转码存入数据库,然后从数据库取出并转码交给客户端显示的实验。
从以上过程我们可以得出以下一些结论:
1.对Oracle数据库存取起作用的是这些:客户端操作系统字符集、客户端操作系统参数NLS_LANG、服务端数据库字符集。
2.对Oracle数据库不起作用的是服务端操作系统字符集。
3.客户端操作系统只有一张编码表,与客户端字符集对应。
4.Oracle数据库的字符集只有一个,并且固定,一般不改变。
5.存放在Oracle数据库中的字符串的编码格式只有一个,它就是数据库的字符集所对应的编码格式。
6.Oracle数据库有很多张编码表,可以在数据存入时将其它编码格式的编码转换为数据库字符集指定的格式,取出时从数据库字符集指定的格式转换为其它编码格式
7.整个架构中的转码只发生在Oracle数据库边界上,其它地方没有。
8.Oracle是根据客户端操作系统的参数NLS_LANG与自己的字符集进行对照来确定是否需要进行转码的。
最最重要的结论出来了:
9.Oracle数据库如何选择字符集?只有一个原则,那就是这个字符集要包含数据库运行过程中所能存入的数据字符,通常作为中国人我们选择ZHS16GBK,如果想再保险一点,选择AL32UTF8。
10.服务器操作系统选择什么字符集?这个字符集与数据库字符集一点关系都没有,只跟谁有关?操作系统管理员!所以它的选择原则是,系统管理员想选择什么就选择什么。
11.客户端操作系统选择什么字符集?我是中国人,我用中文操作系统,所以我选择ZHS16GBK。建议中国人都选择ZHS16GBK。
12.客户端操作系统参数NLS_LANG参数如何设置?这个只有一个设置方法,那就是与操作系统字符集相同。要不然会出问题的……
最最最最重要的一句话:
最好的,最不容易出字符集错误的就是:将数据库字符集、客户端字符集、客户端操作系统NLS_LANG参数三个地方作同样的设置。
另外再记录一下EXP和IMP过程与字符集相关的事情。
EXP时,起作用的有Oracle数据库的字符集和客户端操作系统参数NLS_LANG两项,这时服务器与客户端操作系统字符集都不起作用。如果客户端操作系统参数NLS_LANG与Oracle数据库的字符集相同,那就直接导出,不需要转码,并且导出文件的字符集与上述两项一样;如果客户端操作系统参数NLS_LANG与Oracle数据库的字符集不同,那么导出时Oracle数据库会将数据文件从Oracle数据库的字符集编码格式转码成客户端操作系统参数NLS_LANG指定的编码格式。总而言之一句话:导出文件的字符集格式与导出客户端操作系统参数NLS_LANG一定相同。
IMP时,起作用的仍然是两项,一项是DMP文件第二第三字节指定的字符集,另外一项是Oracle数据库的字符集。两个相同就不需要转码,两个不同就转成Oracle数据库字符集指定的编码格式。
相信看完以上文章定会对Oracle乱码问题的产生原因,有个深刻的认识!当然,因为精力有限,有些转载内容没有实质性校验正确,这边只做记录备忘之用,希望能对看官有所帮助~如有错误之处,还望批评指正,转载请注明出处。本文很多内容非原创,尊重原创,参考网址如下:
http://blog.csdn.net/jovitang/article/details/5174062
http://blog.itpub.net/9240380/viewspace-666071
http://www.cnblogs.com/kerrycode/p/3749085.html
http://www.itpub.net/thread-235335-1-1.html
http://www.askmaclean.com/archives/script-list-nls-parameters-and-timezone.html
http://blog.itpub.net/12131609/viewspace-701225/
http://www.cnblogs.com/wanglibo/articles/2121098.html
http://www.itpub.net/thread-1012118-1-1.html
http://zhidao.baidu.com/link?url=AFYAsfP-7Dg1tS6PNGFrSkJRAnTSov34_8V7Pd2ujbKtsz4X8txKQFyIE4TRIVhjEDHakM-vGln58DHKAIq3j_
http://www.cnblogs.com/cubean/archive/2009/09/24/1573396.html
http://wenku.baidu.com/link?url=rWu8LKOCGdbGZ2kTzvGKzwkJQpUHwFufDNdaR7NGkG2ezpKe5JJLrkIlTppX9xQN9VquV_mXc6WtsfGskTeRHc3Fj2fXPaZDKpcv4RGUsD7
http://blog.csdn.net/meteorlwj/article/details/8057470
http://zhidao.baidu.com/link?url=toKCPyOUL5v9fx_wvSvO3P3TC1CBtwkKendU8b1CAzAPvEa5BWQB0GuVkiXceHY6Va-d9VrIbTFgMjw3WQG4GiqklSlEPgPSsdqYUddEzrm
http://www.educity.cn/shujuku/1176316.html
http://blog.163.com/jiankun_liu/blog/static/1863927762013698175289/