Oracle字符集

这是一个困扰了我很久的问题,以前查了很多资料做了很多实验都不得其精髓,终于在最近有所感悟,整理下来记录与此。

一、背景简介
一些重要的拓展知识:(基于https://www.zhihu.com/question/23374078的答案做了一些修正和改动
ASCII: 用8bit表示英文字符(最多可以表示2^8=256个英文字符),其中127-256之间的数据ASCII的扩展部分。属于ANSI标准。
GB2312: 使用2个字节表示一个中文字符,其中高字节范围为0xA1-0xF7低字节为0xA1-0xFE,可以表示很多汉字。如果单个字节小于等于127则依然表示原来的ASCII英文字母,因此GB2312是对ASCII的一种扩展,也属于ANSI标准,但是把单字节127-256表示的编码砍掉了。
GBK: 只要一个字节大于127(0-127即2^7)那么认为它和之后的一个字节组成一个汉字字符,英文字符依然使用127之前的单字节表示。依然算是ANSI标准。Windows默认的cp936其实就是gbk,只是因为在发明code page时将gbk放在了936页所以叫cp936。
后来为兼容少数民族的文字又进行了扩展,依然使用2字节,称为国标GB18030
各个国家都发展出了基于ASCII兼容本国语言的字符集,都属于ANSI标准
 
此时亟待进行全球化的统一,于是unicode标准出现了:
Unicode: Universal Multiple-Octet Coded Character Set
unicode使用2字节来表示任何文字字符,存储英文时高位补0,这样造成存储和传输的极大浪费,于是UTF8出现,这是一种专为传输而生的,基于unicode的二次编码方式,他使用1-6字节来存储一个unicode字符。这是一种变长的编码方式,使用UTF8编码可以降低存储使用量和传输消耗。
到这里我们基本可以得出一个结论:
ASCII、GBK、Unicode、UTF8这些既是字符集也是编码/解码方式,其种UTF8则有稍许不同,他是基于Unicode字符集的,可以看做是对unicode的二次编码,目的是为了减少unicode的长度浪费和传输消耗,是一种为传输而设计的二次编码规则。
Unicode码转为UTF-8的二次编码格式为(不全):

U+ 0000 ~ U+ 007F: 0XXXXXXX 
U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX 
U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX 
U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

例如“知”的unicode编码为0x77E5,处于UTF-8的第三行范围,因此使用3个字节传输。
77E5的二进制码是0111 0111 1110 0101,因此补位至上述第三行后变为:11100111 10011111 10100101,以这种3字节进行传输。
而且这种UTF-8的编码模式还保留了ASCII的原始编码,因此是兼容ANSI的ASCII标准的!!!但是注意并不兼容也属于ANSI标准的GBK。
MySQL就是直接使用ANSI或Unicode编码的数据库。
此时我们来考虑Oracle的字符集就比较清晰了:
常见的有US7ASCII、AL32UTF8、ZHS16GBK、UTF8等。其格式为<Language><bitsize><encoding>,实际使用的还是ANSI或unicode编码。
Unicode最新标准已经支持最大6个字节来表示一个字符,但是AL32UTF8还是使用最大4字节的编码方式的,中文最终被用3个字节编码。
 
二、关于Linux和Oracle的字符集
1. LANG环境变量
LANG是针对Linux系统的语言、地区、字符集的设置,对Linux下的应用程序有效,如date等。
其配置文件位于/etc/sysconfig/i18n,默认值为LANG=”en-US.UTF8″(Linux7.2中字符集文件变为/etc/locale.conf,删除了/etc/sysconfig/i18n文件)
export LANG=XXX –可以修改当前窗口的系统字符集,例如export LANG=zh_CN.GB18030
vi /etc/sysconfig/i18n  –重启之后可以修改本地客户端操作系统的字符集,默认为en-US.UTF8
 
2. NLS_LANG环境变量
NLS_LANG有三部分:

1 Language:指定服务器消息的语言, 影响提示信息是中文还是英文
2 Territory:指定服务器的日期和数字格式
3 Charset:指定数据字符集
Windows: set nls_lang=AMERICAN_AMERICA.ZHS16GBK
Linux: export NLS_LANG="SIMPLIFIED CHINESE_CHINA".ZHS16GBK

 
3. Oracle字符集
Oracle字符集是甲骨文为了推进其产品的国际化应用而推出的针对不同语言的字符集支持。Oracle数据库最早支持的编码方案是US7ASCII。
Oracle的字符集命名遵循以下命名规则:<Language><bitsize><encoding>,例如ZHS16GBK就是提供中文支持的、一个中文或一个英文都占2字节的、GBK编码的方式。AL32UTF8的意思就是支持所有地区的、一个字符最多占32bit的、UTF8编码,一个中文字符中文占3字节,一个英文字符占一字节。
AL32UTF8 ZHS16GBK的区别:
AL32UTF8是变长编码,存储中文字符使用3个字节,存英文字符用一个字节。ZHS16GBK表示最大用2bytes存储单个字符,中英文字符都占用2个bytes。
关于导入导出的字符集转换:https://docs.oracle.com/cd/B28359_01/server.111/b28298/ch2charset.htm#i1007203

During conversion from one character set to another, Oracle Database expects client-side data to be encoded in the 
character set specified by the NLS_LANG parameter.

在imp导入时Oracle会认为dmp数据是按NLS_LANG中的字符集编码的,因此导入时客户端的字符集一定要设置为与导出库的字符集一致。
Oracle一般不会产生乱码,即便你往US7ASCII字符集的库里imp了中文,只要你的varchar够长不至于发生错位,那么客户端设置对应的字符集就能正确解码显示。(这种情况一般imp也不会成功)
因此在产生乱码时,不要急于推倒重做,先尝试在不同的解码环境下查看数据,然后再确定具体的解决办法。举例来说xshell5里你可以通过如下按钮切换解码方式来查看乱码的数据,要设为与源库相同的字符集。
 

4. 数据库字符集查询和更改
数据库创建时选定的数据存储字符集,创建完毕后几乎无法修改(或者说修改代价极大)。
可以通过如下语句来查看数据库的字符集:
前者表示varchar字符集,后者表示nvarchar字符集,这也是US7ASCII库可以存中文的原因,只要用nvarchar存就可以啦。

select * from v$nls_parameters where parameter in ('NLS_CHARACTERSET','NLS_NCHAR_CHARACTERSET');

通过如下语句更改字符集,更改字符集只能从子集改为超集,关于子集超集关系图参考:
https://docs.oracle.com/cd/B19306_01/server.102/b14225/applocaledata.htm#g681463
修改数据库字符集的流程:(从子集US7ASCII到超集ZHS16GBK 或者 从子集UTF8到超集AL32UTF8)

SQL>SHUTDOWN IMMEDIATE;
SQL>STARTUP MOUNT [EXCLUSIVE];
SQL>ALTER SYSTEM ENABLE RESTRICTED SESSION;
SQL>ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0;
SQL>ALTER SYSTEM SET AQ_TM_PROCESSES=0;
SQL>ALTER DATABASE OPEN;
SQL>ALTER DATABASE CHARACTER SET ZHS16GBK;
SQL>ALTER DATABASE NATIONAL CHARACTER SET ZHS16GBK;
--从子集到超集,可以使用INTERNAL_USE 参数,跳过超子集检测。
SQL>ALTER DATABASE CHARACTER SET INTERNAL_USE AL32UTF8;
SQL>ALTER DATABASE NATIONAL CHARACTER SET INTERNAL_USE AL32UTF8;
SQL>SHUTDOWN IMMEDIATE;
SQL>STARTUP

 
其他:
select userenv(‘language’) from dual;也可以查看数据库varchar字符集。
其中前一部分会与当前环境的NLS_LANG的前一部分一致,后一部分才是数据库字符集。

 

(0)
上一篇 2022年3月22日
下一篇 2022年3月22日

相关推荐