[Oracle] 搞懂Oracle字符集

By | 06月24日
Advertisement

基本概念

字符集(Character set):是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。常见的字符集有ASCII,ZHS16GB231280,ZHS16GBK等。

字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其它的一个集合(如电脑编码)进行配对。即在符号集合与数字系统之间建立对应关系。与字符集相对应,常见的字符编码有:ASCii,ZHS16GBK,ZHT16BIG5,ZHS32GB18030等。

字符集的定义其实就是字符的集合,而字符编码则是指怎么将这些字符变成字节用于保存、读取和传输。

万国码(Unicode):包含了几乎人类所有可用的字符,每年还在不断的增加,可以看作是一种通用的字符集。它将全世界所有的字符统一化,统一编码,不会再出现字符不兼容和字符转换的问题。它有以下三种编码方式:

  1. UTF-32编码:固定使用4个字节来表示一个字符,存在空间利用效率的问题。
  2. UTF-16编码:对相对常用的60000余个字符使用两个字节进行编码,其余的使用4字节。
  3. UTF- 8编码:兼容ASCII编码;拉丁文、希腊文等使用两个字节;包括汉字在内的其它常用字符使用三个字节;剩下的极少使用的字符使用四个字节。

Oracle字符集基本原理

在搞懂Oracle字符集基本原理之前,一定要先分清以下三个概念:

1. Oracle数据库服务器字符集:即Oracle以哪种字符编码存储字符,可以通过以下语句查出数据库字符集的设置。

SQL> select * from v$nls_parameters where parameter='NLS_CHARACTERSET';

PARAMETER                      VALUE
------------------------------ -----------------
NLS_CHARACTERSET               AL32UTF8

2. 客户端操作系统字符集:即客户端操作系统以哪种字符编码存储字符。

如果是Windows,可以使用chcp命令获得代码页(code page):

C:\Users\xianzhu>chcp
Active code page: 936

根据该代码页,到微软的官方文档《National Language Support (NLS) API Reference》找到其对应的字符集。

如果是Linux,字符集在/etc/sysconfig/i18n设置:

LANG="zh_CN.GB2312" (指定当前操作系统的字符集)
SUPPORTED="zh_CN.GB2312"(指定当前操作系统支持的字符集)
SYSFONT="lat0-sun16"(指定当前操作系统的字体)

3. 客户端NLS_LANG参数:该参数用于向Oracle指示客户端操作系统的字符集。

有了以上3个基本概念之后,我来阐述一下Oracle字符集转换的基本原则:

  1. 设置客户端的NLS_LANG为客户端操作系统的字符集
  2. 如果数据库字符集等于NLS_LANG,数据库和客户端传输字符时不作任何转换
  3. 如果它们俩不等,则需要在不同字符集间转换,只有客户端操作系统字符集是数据库字符集子集的基础上才能正确转换,否则会出现乱码。

几种常见情况分析

下面先看一个例子,再透过现象看本质,我们会针对这个例子进行分析。

该例子如下:

1. 数据库字符集为Unicode(UTF-8编码)
我们的数据库版本是10.2.0.4.0,数据库字符集是:
SQL> select * from v$nls_parameters where parameter='NLS_CHARACTERSET';

PARAMETER                                VALUE
---------------------------------------- ------------------------------
NLS_CHARACTERSET               AL32UTF8

2. 客户端操作系统字符集为代码页936(字符集为ZHS16GBK)
可以使用chcp获得windows的代码页(code page)
C:\Documents and Settings\a105024\Desktop>chcp
Active code page: 936

3. 创建测试表
SQL> create table test(id number,var varchar2(30));

Table created.

4. 插入数据
这里在同一个操作系统启动两个session,session1的NLS_LANG设为和数据库字符集一样(即AL32UTF8):
C:\Documents and Settings\a105024\Desktop>set nls_lang=Simplified Chinese_China.AL32UTF8
连接数据库并插入一条数据:
Session_1>insert into test values(1,'中国');

1 row created.

Session_1>commit;

Commit complete.

session2的NLS_LANG设为和客户端操作系统一样(即ZHS16GBK):
C:\Documents and Settings\a105024\Desktop>set nls_lang=Simplified Chinese_China.ZHS16GBK
连接数据库并插入一条数据:
Session_2>insert into test values(2,'中国');

1 row created.

Session_2>commit;

Commit complete.

5. 执行查询
在session 1上执行查询:
Session_1>select * from test;

        ID VAR
---------- ---------------------
         1 中国
         2 涓   浗
在session 2上执行查询:
Session_2>select * from test;

        ID VAR
---------- --------------------
         1 ???
         2 中国

上面例子看起来很诡异,session1和2都能正常显示自己插入的字符串,又都不能正常显示对方插入的字符串。为了弄清楚,我们首先得知道数据库里对这两个字符串是怎么存储的。我们可以使用dump函数获得字符在数据库的编码:

SQL> select id,dump(var,1016) from test;
ID DUMP(VAR,1016)
-- ------------------------------------------------------------
 1 Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
 2 Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd

根据AL32UTF8的编码,“中国”两字的正确编码为(都为3个字节):
中--e4,b8,ad
国--e5,9b,bd
因此session 1插入的字符串在数据库中的编码是错误的,session 2正确。这也是为什么一定要设置NLS_LANG为客户端操作系统的字符集。

但是根据上面实验我们可以知道,数据库中存储正确,并不代表客户端能正常显示;同样地,即时数据库没有正确存储,有时候客户端也能够正常显示,这又是为什么呢?别急,请听我慢慢道来:

场景1:session 1插入,session 1查询,在数据库中存储错误,但显示正确。
插入过程:
”中国“两字在客户端操作系统字符集ZHS16GBK中的编码是”d6,d0,b9,fa",由于NLS_LANG和数据库字符集相同,数据库端对客户端传过来的字符编码不进行任何转换直接存入数据库,因此数据库中存储的编码也是“d6,d0,b9,fa”,
读取过程:
数据库端读取的编码是“d6,d0,b9,fa”,由于NLS_LANG和数据库字符集相同,客户端对数据库端传过来的字符编码不进行任何转换直接显示,编码”d6,d0,b9,fa“在客户端操作系统字符集ZHS16GBK对应的汉字为“中国”。
从以上分析可知,虽然读取时正确的,但那是因为负负得正,实际上数据库中存储是错误的,因此要特别小心这种情况,在生成库中要避免。其实只要对它进行length操作就能轻易揭开它的假面具:

Session_1>select length(var) from test where id=1;

LENGTH(VAR)
-----------
          3

得出的长度居然为3!实际的长度只是2,这会带来很多麻烦。

场景2:session 1插入,session 2查询,在数据库中存储错误,显示也错误。
插入过程和场景1一样,这里就不再累述。
读取过程:
数据库端读取的编码是“d6,d0,b9,fa”,由于NLS_LANG和数据库字符集不同,客户端对数据库端传过来的字符编码进行转换,数据库端字符集AL32UTF8里编为“d6,d0,b9,fa”无法在客户端操作系统字符集ZHS16GBK里找到对应的编码,所以只好用?代替。

场景3:session 2插入,session 1查询,在数据库中存储正确,但显示错误。
插入过程:
”中国“两字在客户端操作系统字符集ZHS16GBK中的编码是”d6,d0,b9,fa",由于NLS_LANG和数据库字符集不同,Oracle会进行字符编码转换,也就是将字符集ZHS16GBK里“中国”的编码“d6,d0,b9,fa"转换为字符集"AL32UTF8"里”中国“的编码”e4,b8,ad,e5,9b,bd“。
读取过程:
数据库端读取的编码是”e4,b8,ad,e5,9b,bd“,由于NLS_LANG和数据库字符集相同,客户端对数据库端传过来的字符编码不进行任何转换直接显示,编码”e4,b8,ad,e5,9b,bd“在客户端操作系统字符集ZHS16GBK对应的汉字为“涓 浗”(原本2个字符,现在变成了3个字符,因为ZHS16GBK的汉字以2个字节编码)。

场景4:session 2插入,session 2查询,在数据库中存储正确,显示也正确。
插入过程和场景3类似。
读取过程:

数据库端读取的编码是”e4,b8,ad,e5,9b,bd“,由于NLS_LANG和数据库字符集不同,客户端对数据库端传过来的字符编码进行转换,数据库端字符集AL32UTF8里”中国“两字的编码”e4,b8,ad,e5,9b,bd“转换成客户端操作系统字符集ZHS16GBK里“中国”两字的编码“d6,d0,b9,fa",并正常显示。

这种情况虽然经过了两次转换,都确实最正确、最推荐的方式。

附录:Oracle 字符集超集和子集的对应关系可查看:http://download.oracle.com/docs/cd/B19306_01/server.102/b14225/applocaledata.htm#sthref1988

结论:NLS_LANG只和客户端操作系统的字符集相关,如果客户端操作系统的字符集和数据库字符集间无法正确转换,则应该首先改变客户端终端的字符集,而不是简单地把NLS_LANG设为和数据库字符集一样。

Similar Posts:

  • Oracle字符集和国家字符集

    一.什么是Oracle字符集 Oracle字符集是一个字节数据的解释的符号集合,有大小之分,有相互的包容关系.ORACLE 支持国家语言的体系结构允许你使用本地化语言来存储,处理,检索数据.它使数据库工具,错误消息,排序次序,日期,时间,货币,数字,和日历自动适应本地化语言和平台. 影响Oracle数据库字符集最重要的参数是NLS_LANG参数. 它的格式如下: NLS_LANG = language_territory.charset 它有三个组成部分(语言.地域和字符集),每个成分控制了NL

  • ORACLE修改数据库的字符集编码为UTF-8方法

    ORACLE修改数据库的字符集编码为UTF-8方法 ORACLE修改数据库的字符集编码为UTF-8方法 1.查看数据库字符集 ? 数据库服务器字符集select * from nls_database_parameters,其来源于props$,是表示数据库的字符集. 客户端字符集环境select * from nls_instance_parameters,其来源于v$parameter, 表示客户端的字符集的设置,可能是参数文件,环境变量或者是注册表 会 话字符集环境 select * fr

  • 搞懂buffer cache

    搞懂buffer cache 虽然随着Oracle版本的推进,内存管理变的不在那么复杂,9i的PGA自动管理(pga_aggregate_target),10g的SGA自动管理(sga_target),到11g把PGA和SGA都纳入自动管理的范畴(memory_target),但是Oracle内存的内部运行机制很值得去探索,能够清晰的认识Oracle每个操作的运行方式,从基层去解决表象的问题,才是正道. SGA中的数据高速缓存区(buffer cache) Oracle修改块的规则并不是直接去修

  • 【oracle案例】oracle EM和isqlplus 无法打开, JDK冲突

    背景: 前些天安装了JDK,并设置了相应的环境变量. JDK安装路径 C:/Program Files/Java/jdk1.6.0_25 JDK环境变量如下: JAVA_HOME=C:/Program Files/Java/jdk1.6.0_25 CLASSPATH=.;%JAVA_HOME%/lib/dt.jar;%JAVA_HOME%/lib/tools.jar PATH中添加java.exe,javac.exe的路径,加入;%JAVA_HOME%/bin 预备知识 oracle自己也带有一

  • Linux 6.4 + Oracle 11.2g Oracle 简易安装步骤

    准备工作,安装软件包 ---------------------------------------------------------------------------------------- 本地网络参数配置 ---------------------------------------------------------------------------------------- 1.设定主机名称(HOSTNAME) #vi /etc/sysconfig/network NETWOR

  • Oracle Study之--Oracle触发器(Trigger)

    Oracle Study之--Oracle触发器(Trigger) 转发自:http://blog.csdn.net/indexman/article/details/8023740/(感谢作者) 本篇主要内容如下: 8.1 触发器类型 8.1.1 DML触发器 8.1.2 替代触发器 8.1.3 系统触发器 8.2 创建触发器 8.2.1 触发器触发次序 8.2.2 创建DML触发器 8.2.3 创建替代(INSTEAD OF)触发器 8.2.3 创建系统事件触发器 8.2.4 系统触发器事件

  • 脚本概念是什么,一直没搞懂

    脚本的概念是什么?一直没搞懂,能用通俗的话总结下吗? --cut-- 粥米鱼在2015-09-03 02:16:52回答到: 脚本就是一段可执行的解析型代码. 例如你在命令行里输入 touch a.txt //新建a.txt文件 继续又输入 echo "hello world" >> a.txt //输出hello world到a.txt文件 然后如果你嫌弃每次都这样操作麻烦,就可以写成一个脚本,把这一些列操作集合起来,下次就直接运行这个脚本就行了 脚本 #!/bin/ba

  • 每个初学者都应该搞懂的问题

    对于这个系列里的问题,每个学Java的人都应该搞懂.当然,如果只是学Java玩玩就无所谓了.如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列.内容均来自于CSDN的经典老贴. 问题一:我声明了什么! String s = "Hello world!"; 许多人都做过这样的事情,但是,我们到底声明了什么?回答通常是:一个String,内容是"Hello world!".这样模糊的回答通常是概念不清的根源.如果要准确的回答,一半的人大概会回答错

  • Oracle 补丁全集 (Oracle 9i 10g 11g Path)

    ****************************************** Oracle 补丁全集 (Oracle 9i 10g 11g Path) ****************************************** 俗话说,金无赤足,人无完人. Oracle 的补丁也是层出不穷.下面将其罗列,或许你就用到了. 从 Oracle 官方网站下载需要使用 Metalink 帐号,下面的下载链接可以直接使用迅雷来下载,对于没 Metalink 帐号的用户可谓是一大福音. *

  • Oracle Study之--Oracle 11gR2 RAC crs启动故障

    Oracle Study之--Oracle 11gR2 RAC crs启动故障 系统环境: 操作系统:RedHat EL5 Cluster: Oracle 11gR2 Grid Oracle: Oracle 11gR2 RAC环境中,其中一个节点crsd进程启动失败: [grid@wyb666 ~]$ crsctl check crs CRS-4638: Oracle High Availability Services is online CRS-4535: Cannot communicat

Tags: