`
benx
  • 浏览: 271979 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Oracle数据库字符集问题解析(转)

阅读更多

Oracle数据库字符集问题解析

经常看到一些朋友问ORACLE字符集方面的问题,我想以迭代的方式来介绍一下。

第一次迭代:掌握字符集方面的基本概念。 
有些朋友可能会认为这是多此一举,但实际上正是由于对相关基本概念把握不清,才导致了诸多问题和疑问。
首先是字符集的概念。
我们知道,电子计算机最初是用来进行科学计算的(所以叫做“计算机”),但随着技术的发展,还需要计算机进行其它方面的应用处理。这就要求计算机不仅能处理数值,还能处理诸如文字、特殊符号等其它信息,而计算机本身能直接处理的只有数值信息,所以就要求对这些文字、符号信息进行数值编码,最初的字符集是我们都非常熟悉的ASCII,它是用7个二进制位来表示128个字符,而后来随着不同国家、组织的需要,出现了许许多多的字符集,如表示西欧字符的ISO8859系列的字符集,表示汉字的GB2312-80、GBK等字符集。
字符集的实质就是对一组特定的符号,分别赋予不同的数值编码,以便于计算机的处理。
字符集之间的转换。字符集多了,就会带来一个问题,比如一个字符,在某一字符集中被编码为一个数值,而在另一个字符集中被编码为另一个数值,比如我来创造两个字符集demo_charset1与demo_charset2,在demo_charset1中,我规定了三个符号的编码为:A(0001),B(0010),?(1111);而在demo_charset2中,我也规定了三个符号的编码为:A(1001),C(1011),?(1111),这时我接到一个任务,要编写一个程序,负责在demo_charset1与demo_charset2之间进行转换。由于知道两个字符集的编码规则,对于demo_charset1中的0001,在转换为demo_charset2时,要将其编码改为1001;对于demo_charset1中的1111,转换为demo_charset2时,其数值不变;而对于demo_charset1中的0010,其对应的字符为B,但在demo_charset2没有对应的字符,所以从理论上无法转换,对于所有这类无法转换的情况,我们可以将它们统一转换为目标字符集中的一个特殊字符(称为“替换字符”),比如在这里我们可以将?作为替换字符,所以B就转换为了?,出现了信息的丢失;同样道理,将demo_charset2的C字符转换到demo_charset1时,也会出现信息丢失。
所以说,在字符集转换过程中,如果源字符集中的某个字符在目标字符集中没有定义,将会出现信息丢失。
数据库字符集的选择。
我们在创建数据库时,需要考虑的一个问题就是选择什么字符集与国家字符集(通过create database中的CHARACTER SET与NATIONAL CHARACTER SET子句指定)。考虑这个问题,我们必须要清楚数据库中都需要存储什么数据,如果只需要存储英文信息,那么选择US7ASCII作为字符集就可以;但是如果要存储中文,那么我们就需要选择能够支持中文的字符集(如ZHS16GBK);如果需要存储多国语言文字,那就要选择UTF8了。
数据库字符集的确定,实际上说明这个数据库所能处理的字符的集合及其编码方式,由于字符集选定后再进行更改会有诸多的限制,所以在数据库创建时一定要考虑清楚后再选择。
而我们许多朋友在创建数据库时,不考虑清楚,往往选择一个默认的字符集,如WE8ISO8859P1或US7ASCII,而这两个字符集都没有汉字编码,所以用这种字符集存储汉字信息从原则上说就是错误的。虽然在有些时候选用这种字符集好象也能正常使用,但它会给数据库的使用与维护带来一系列的麻烦,在后面的迭代过程中我们将深入分析。
客户端的字符集。
有过一些Oracle使用经验的朋友,大多会知道通过NLS_LANG来设置客户端的情况,NLS_LANG由以下部分组成:NLS_LANG=<Language>_<Territory>.<Clients Characterset>,其中第三部分<Clients Characterset>的本意就是用来指明客户端操作系统缺省使用的字符集。所以按正规的用法,NLS_LANG应该按照客户端机器的实际情况进行配置,尤其对于字符集一项更是如此,这样Oracle就能够在最大程度上实现数据库字符集与客户端字符集的自动转换(当然是如果需要转换的话)。
总结一下第一次迭代的重点:
字符集:将特定的符号集编码为计算机能够处理的数值;
字符集间的转换:对于在源字符集与目标字符集都存在的符号,理论上转换将不会产生信息丢失;而对于在源字符集中存在而在目标字符集中不存在的符号,理论上转换将会产生信息丢失;
数据库字符集:选择能够包含所有将要存储的信息符号的字符集;
客户端字符集设置:指明客户端操作系统缺省使用的字符集。

第二次迭代:通过实例加深对基本概念的理解

下面我将引用网友tellin在ITPUB上发表的“CHARACTER SET研究及疑问”帖子,该朋友在帖子中列举了他做的相关实验,并对实验结果提出了一些疑问,我将对他的实验结果进行分析,并回答他的疑问。
实验结果分析一

QUOTE:
最初由 tellin 发布
设置客户端字符集为US7ASCII 
D:\>SET NLS_LANG=AMERICAN_AMERICA.US7ASCII
查看服务器字符集为US7ASCII 
SQL> SELECT * FROM NLS_DATABASE_PARAMETERS;
PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_CHARACTERSET               US7ASCII 

    建立测试表
SQL> CREATE TABLE TEST (R1 VARCHAR2(10));

Table created.

    插入数据
SQL> INSERT INTO TEST VALUES('东北');

1 row created.

SQL> SELECT * FROM TEST;

R1
----------
东北

SQL> EXIT



这一部分的实验数据的存取与显示都正确,好象没什么问题,但实际上却隐藏着很大的隐患。
首先,要将汉字存入数据库,而将数据库字符集设置为US7ASCII是不合适的。US7ASCII字符集只定义了128个符号,并不支持汉字。另外,由于在SQL*PLUS中能够输入中文,操作系统缺省应该是支持中文的,但在NLS_LANG中的字符集设置为US7ASCII,显然也是不正确的,它没有反映客户端的实际情况。
但实际显示却是正确的,这主要是因为Oracle检查数据库与客户端的字符集设置是同样的,那么数据在客户与数据库之间的存取过程中将不发生任何转换。具体地说,在客户端输入“东北”,“东”的汉字的编码为182(10110110)、171(10101011),“北”汉字的编码为177(10110001)、177(10110001),它们将不做任何变化的存入数据库中,但是这实际上导致了数据库标识的字符集与实际存入的内容是不相符的,从某种意义上讲,这也是一种不一致性,也是一种错误。而在SELECT的过程中,Oracle同样检查发现数据库与客户端的字符集设置是相同的,所以它也将存入的内容原封不动地传送到客户端,而客户端操作系统识别出这是汉字编码所以能够正确显示。
在这个例子中,数据库与客户端的设置都有问题,但却好象起到了“负负得正”的效果,从应用的角度看倒好象没问题。但这里面却存在着极大的隐患,比如在应用length或substr等字符串函数时,就可能得到意外的结果。另外,如果遇到导入/导出(import /export)将会遇到更大的麻烦。有些朋友在这方面做了大量的测试,如eygle研究了“源数据库字符集为US7ASCII,导出文件字符集为US7ASCII或ZHS16GBK,目标数据库字符集为ZHS16GBK”的情况,他得出的结论是 “如果的是在Oracle92中,我们发现对于这种情况,不论怎样处理,这个导出文件都无法正确导入到Oracle9i数据库中”、“对于这种情况,我们可以通过使用Oracle8i的导出工具,设置导出字符集为US7ASCII,导出后修改第二、三字符,修改 0001 为0354,这样就可以将US7ASCII字符集的数据正确导入到ZHS16GBK的数据库中”。我想对于这些结论,这样理解可能更合适一些:由于ZHS16GBK字符集是US7ASCII的超级,所以如果按正常操作,这种转换应该没有问题;但出现问题的本质是我们让本应只存储英文字符的US7ASCII数据库,非常规地存储了中文信息,那么在转化过程中出现错误或麻烦就没什么奇怪的了,不出麻烦倒是有些奇怪了。
所以说要避免这种情况,就是要在建立数据库时选择合适的字符集,不让标签(数据库的字符集设置)与实际(数据库中实际存储的信息)不符的情况发生。

实验结果分析二



QUOTE:
更改客户端字符集为ZHS16GBK
D:\>SET NLS_LANG=AMERICAN_AMERICA.ZHS16GBK

D:\>SQLPLUS "/ AS SYSDBA"

无法正常显示数据

SQL> SELECT * FROM TEST;

R1
--------------------
6+11

疑问1:ZHS16GBK为US7ASCII的超集,为什么在ZHS16GBK环境下无法正常显示

这主要是因为Oracle检查发现数据库设置的字符集与客户端配置字符集不同,它将对数据进行字符集的转换。数据库中实际存放的数据为182(10110110)、171(10101011)、177(10110001)、177(10110001),由于数据库字符集设置为US7ASCII,它是一个7bit的字符集,存储在8bit的字节中,则Oracle忽略各字节的最高bit,则182(10110110)就变成了54(0110110),在ZHS16GBK中代表数字符号“6”(当然在其它字符集中也是“6”),同样过程也发生在其它3个字节,这样“东北”就变成了“6+11”。


实验结果分析三



QUOTE:
最初由 tellin 发布
用ZHS16GBK插入数据
SQL> INSERT INTO TEST VALUES('东北');

1 row created.

SQL> SELECT * FROM TEST;

R1
--------------------
6+11
??

SQL> EXIT

当客户端字符集设置为ZHS16GBK后向数据库插入“东北”,Oracle检查发现数据库设置的字符集为US7ASCII与客户端不一致,需要进行转换,但字符集ZHS16GBK中的“东北”两字在US7ASCII中没有对应的字符,所以Oracle用统一的“替换字符”插入数据库,在这里为“?”,编码为63(00111111),这时,输入的信息实际上已经丢失,不管字符集设置如何改变(如下面引用的实验结果),第二行SELECT出来的结果也都是两个“?”号(注意是2个,而不是4个)。

QUOTE:
更改客户端字符集为US7ASCII 
D:\>SET NLS_LANG=AMERICAN_AMERICA.US7ASCII

D:\>SQLPLUS "/ AS SYSDBA"

无法显示用ZHS16GBK插入的字符集,但可以显示用US7ASCII插入的字符集
SQL> SELECT * FROM TEST;

R1
----------
东北
??


更改服务器字符集为ZHS16GBK
SQL> update props$ set value$='ZHS16GBK' WHERE NAME='NLS_CHARACTERSET';

1 row updated.

SQL> COMMIT;

更改客户端字符集为ZHS16GBK
D:\>SET NLS_LANG=AMERICAN_AMERICA.ZHS16GBK

D:\>SQLPLUS "/ AS SYSDBA"

可以显示以前US7ASCII的字符集,但无法显示用ZHS16GBK插入的数据,说明用ZHS16GBK插入的数据为乱码。

SQL> SELECT * FROM TEST;

R1
--------------------
东北
??

需要指出的是,通过“update props$ set value$='ZHS16GBK' WHERE NAME='NLS_CHARACTERSET';”来修改数据库字符集是非常规作法,很可能引起问题,在这里只是原文引用网友的实验结果。



实验结果分析四



QUOTE:
SQL> INSERT INTO TEST VALUES('东北');

1 row created.

SQL> SELECT * FROM TEST;

R1
--------------------
东北
??
东北

SQL> EXIT

由于此时数据库与客户端的字符集设置均为ZHS16GBK,所以不会发生字符集的转换,第一行与第三行数据显示正确,而第二行由于存储的数据就是63(00111111),所以显示的是“?”号。

QUOTE:
更改客户端字符集为US7ASCII

D:\>SET NLS_LANG=AMERICAN_AMERICA.US7ASCII

D:\>SQLPLUS "/ AS SYSDBA"

无法显示数据

SQL> SELECT * FROM TEST;

R1
----------
??
??
??

疑问2:第一行数据是用US7ASCII环境插入的,为何无法正常显示?

将客户端字符集设置改为US7ASCII后进行SELECT,Oracle检查发现数据库设置的字符集为ZHS16GBK,数据需要进行字符集转换,而第一行与第三行的汉字“东”与“北”在客户端字符集US7ASCII中没有对应字符,所以转换为“替换字符”(“?”),而第二行数据在数据库中存的本来就是两个“?”号,所以虽然在客户端显示的三行都是两个“?”号,但在数据库中存储的内容却是不同的。

实验结果分析五



QUOTE:
SQL> INSERT INTO TEST VALUES('东北');

1 row created.

SQL> EXIT
更改客户端字符集为ZHS16GBK
D:\>SET NLS_LANG=AMERICAN_AMERICA.ZHS16GBK


D:\>SQLPLUS "/ AS SYSDBA"

无法显示用US7ASCII插入的字符集,但可以显示用ZHS16GBK插入的字符集
SQL> SELECT * FROM TEST;

R1
--------------------
东北
??
东北
6+11

SQL>
疑问3:US7ASCII为ZHS16GBK的子集,为何在US7ASCII环境下插入的数据无法显示?

在客户端字符集设置为US7ASCII时,向字符集为ZHS16GBK的数据库中插入“东北”,需要进行字符转换,“东北”的ZHS16GBK编码为182(10110110)、171(10101011)与177(10110001)、177(10110001),由于US7ASCII为7bit编码,Oracle将这两个汉字当作四个字符,并忽略各字节的最高位,从而存入数据库的编码就变成了54(00110110)、43(00101011)与49(00110001)、49(00110001),也就是“6+11”,原始信息被改变了。这时,将客户端字符集设置为ZHS16GBK再进行SELECT,数据库中的信息不需要改变传到客户端,第一、三行由于存入的信息没有改变能显示“东北”,而第二、四行由于插入数据时信息改变,所以不能显示原有信息了。



分析了这么多的内容,但实际上总结起来也很简单

分析了这么多的内容,但实际上总结起来也很简单,要想在字符集方面少些错误与麻烦,需要坚持两条基本原则:
在数据库端:选择需要的字符集(通过create database中的CHARACTER SET与NATIONAL CHARACTER SET子句指定);
在客户端:设置操作系统实际使用的字符集(通过环境变量NLS_LANG设置)。

utf8 全包容了bgk ,并不是说超集和子集;
8i 的数据库 的 utf8 是  4位定长的字符编码;
9i 和以上数据库的utf8   有4位定长AL32UTF8和  不定长的  UTF8  ,都是unicode编码;
其中,utf8 编码中 字符大多是三字节的编码,一个汉字是  3字节的编码;
zhs16gbk  并不是unicode 编码,只是提供了对gbk汉字编码的支持, 一个汉字为双字节;
utf8  对于 zhs16gbk  中的所有编码都有唯一的编码以之对应,所以我说utf8 全包容了bgk;
当然,utf8作为一种unicode 编码,他还提供  global character  的支持;
假如设置得当,从  zhs15gbk 到utf8  是可以实现无损耗的字符转换的;


查看数据库字符集

数据库服务器字符集select * from nls_database_parameters,其来源于props$,是表示数据库的字符集。
  
  客户端字符集环境select * from nls_instance_parameters,其来源于v$parameter,
  
  表示客户端的字符集的设置,可能是参数文件,环境变量或者是注册表
  
  会话字符集环境 select * from nls_session_parameters,其来源于v$nls_parameters,表示会话自己的设置,可能是会话的环境变量或者是alter session完成,如果会话没有特殊的设置,将与nls_instance_parameters一致。
  
  客户端的字符集要求与服务器一致,才能正确显示数据库的非Ascii字符。如果多个设置存在的时候,alter session>环境变量>注册表>参数文件
  
  字符集要求一致,但是语言设置却可以不同,语言设置建议用英文。如字符集是zhs16gbk,则nls_lang可以是American_America.zhs16gbk。

 

分享到:
评论

相关推荐

    Oracle数据库字符集问题解析

    Oracle数据库字符集问题解析

    Oracle数据库字符集问题解析.pdf

    Oracle数据库字符集问题解析.pdf

    Oracle数据库字符集问题解析(1)

    Oracle数据库字符集问题解析(1)

    赤兔Oracle数据库恢复软件 v11.6.zip

    软件功能强大,持修复因各种原因造成的数据库无法打开或数据库删除后没有备份的问题,从而实现对Oracle数据库的抢修恢复,最大限度减少数据丢失。是用户实现Oracle数据库抢修恢复的好帮手。需要的朋友快来下载吧! ...

    oracle数据库修复

    不需要运行Oracle数据库软件,ODU直接读取数据库文件解析数据。 支持ASM,能够直接从ASM磁盘中导出数据,即使相关的磁盘组不能成功mount 支持从ASM中直接抽取出数据文件和其他任意存储在ASM中的文件(包括控制文件...

    mysql数据库中字符集乱码问题原因及解决

    实际上,无论何种数据库只要出现乱码问题,这大多是由于数据库字符集设定的问题。 下面我们就介绍一下,数据库的字符集的设定及乱码问题的解决。 mysql数据库的字符集 直白的说,字符就像是单个的文字,编码就像是给...

    Oracle+10g应用指导与案例精讲

    第10章 数据库管理,包括导出数据库模式的DDL脚本,管理Oracle数据库实例的方法、数据恢复与Flashback以及更改数据库字符集等具体方法。第11章 数据库的安全管理。包括加密Oracle子程序,存储应用程序用户名和口令,...

    ITPUB电子杂志第11期-论坛精华内容选辑

    1.Oracle基本数据类型存储格式浅析 2.一次SQL Tuning引出来的not in , not exists 语句的N种写法 3.REF CURSOR 小结 ...5.Oracle数据库字符集问题解析 6.关于Oracle10g跨平台传输表空间 7.oracle常用命令(学习笔记)

    Oracle 10g应用指导

    第10章 数据库管理,包括导出数据库模式的DDL脚本,管理Oracle数据库实例的方法、数据恢复与Flashback以及更改数据库字符集等具体方法。第11章 数据库的安全管理。包括加密Oracle子程序,存储应用程序用户名和口令,...

    oracle中读写blob字段的问题解析

    而CLOB,即字符型大型对像(Character Large Object),则与字符集相关,适于存贮文本型的数据(如歷史档案、大部头著作等)。 下面以程序实例说明通过JDBC操纵Oracle数据库LOB类型字段的几种情况。 先建立如下两个...

    OCPOCA认证考试指南全册:Oracle Database 11g(1Z0-051,1Z0-052,1Z0-053)--详细书签版(第2/2部分)

     Bob Bryla是Oracle 9i和10g的认证专家,他在数据库设计、数据库应用程序开发、培训和Oracle数据库管理等方面拥有20多年的工作经验,他也足Dodgeville的Land'End公司的首席Internet数据库设计师和Oracle DBA. ...

    groovy将JDBC中oracle存储过程游标转换为多层json

    入参是字符串格式的xml,目的是解析xml节点值,作为数据库检索where条件,检索出数据库记录,利用四个游标返回四个数据集,然后用groovy脚本将四个游标数据集解析成json出参中对应的四个节点值。出参json有两层结构...

    Oracle Stream-安装配置

    Oracle Stream利用高级队列技术,通过解析归档日志,将归档日志解析成DDL及DML语句,从而实现数据库之间的同步。这种技术可以将整个数据库、数据库中的对象复制到另一数据库中,通过使用Stream的技术,对归档日志的...

    OCPOCA认证考试指南全册:Oracle Database 11g(1Z0-051,1Z0-052,1Z0-053)--详细书签版(第1/2部分)

     Bob Bryla是Oracle 9i和10g的认证专家,他在数据库设计、数据库应用程序开发、培训和Oracle数据库管理等方面拥有20多年的工作经验,他也足Dodgeville的Land'End公司的首席Internet数据库设计师和Oracle DBA. ...

    PHP bible(PHP圣经)中文版

    Oracle 8 数据库函数库 Oracle 数据库函数库 Perl 相容语法函数库 PDF 格式文件函数库 PostgreSQL 数据库函数库 正则表达式函数库 信号与共享内存函数库 Solid 数据库链接函数库 SNMP 网管函数库 字符串...

    PHP开发实战1200例(第1卷).(清华出版.潘凯华.刘中华).part1

    实例112 解决用substr()函数对中文字符串截取时出现乱码的问题 143 实例113 字符串与HTML标记相互转换 144 实例114 运用PHP 5.0新型字符串输出XML数据 145 实例115 判断字符串中是否存在指定子串 146 2.9 正则表达式...

    PHP开发实战1200例(第1卷).(清华出版.潘凯华.刘中华).part2

    实例112 解决用substr()函数对中文字符串截取时出现乱码的问题 143 实例113 字符串与HTML标记相互转换 144 实例114 运用PHP 5.0新型字符串输出XML数据 145 实例115 判断字符串中是否存在指定子串 146 2.9 正则表达式...

    Oracle生成不重复票号与LPAD,RPAD与NEXTVAL函数解析

    SELECT TO_CHAR(SYSDATE,...DUAL : 是oracle的虚拟表,不是真实存在的,又称ORCLE伪表,方便输出结果集 REFUNDSEQ : 这个是开发人员自己起的一个”序列”的名字,序列一般用于生成id号 REFUNDSEQ.NEXTVAL : 就是取序

Global site tag (gtag.js) - Google Analytics