Oracle数据库的字符集问题,也涉及作为服务器操作系统的CentOS或者Windows的字符集与Oracle字符集之间的关联关系
Oracle的字符集,这个问题的提出是因为两个原因:
一是遇到一个DMP文件需要恢复到数据库中去,而这个DMP文件的字符集是US7ASCII,
第二个是系统上安装Oracle已经能成功,
但是被中文英文系统字符集等问题搞得有点头大。
Oracle数据库的字符集问题不算是大问题,但也是一个头疼的问题。
这是因为有这么三个原因:
一是Oracle数据库在安装时指定好字符集之后一般不能更改,
二是字符集问题涉及服务器与客户端之间的存取问题,
三是Oracle数据库迁移时也会跟字符集非常相关。
清楚Oracle字符集的相关问题,则要先理清数据库运行过程中的架构以及在这个架构中的字符集设置及这些设置之间的关联关系。
服务器与客户端分开,客户端用应用程序比如sqlplus或者PL/SQL与服务端相连。
服务端有两个字符集:服务端操作系统字符集、服务端数据库字符集;
客户端有一个字符集:客户端操作系统字符集;
客户端有一个参数:操作系统参数NLS_LANG。
这三个字符集与一个参数中,有一个字符集对整个架构的运行没有影响,它就是服务端操作系统字符集,所以这个字符集将不再出现在我们的讨论过程中。为什么这个服务端操作系统字符集没有用呢?
这是因为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数据库字符集指定的编码格式。
最后记录几个问题。
1.DMP文件是US7ASCII字符集,要把它导入字符集是ZHS16GBK的数据库中去,如何操作?
第一步:安装一个US7ASCII字符集的数据库(比如说9i);
第二步,将DMP文件导入该数据库;
第三步,设置导出客户端操作系统参数NLS_LANG=ZHS16GBK,然后导出;
第四步,将后导出的DMP文件导入字符集是ZHS16GBK的数据库中去。理论上成功。需要做实验测试。
2.曾经说到,一般情况下数据库的字符集在数据库安装好之后就不可以更改。那么如果万一领导说一定要改,怎么办?
比如说原来的字符集是ZHS16GBK,非要让转成UTF8,有没有办法?
答案是有的,但是,但是不一定会全部成功,这里有一个严格超集的概念,这个概念在这篇文章里不讲。
答案是这么做,设置导出客户端操作系统参数为UTF8,然后导出,这里,数据编码格式会从ZHS16GBK转码成UTF8,
然后再删除ZHS16GBK的数据库,新建一个UTF8的数据库,再导入就可以了。
oracle中导入dmp字符乱码分析和解决方案
oracle字符集
Oracle字符集是一个字节数据的解释的符号集合,有大小之分,有相互的包容关系。
ORACLE支持国家语言的体系结构允许你使用本地化语言来存储,处理,检索数据。
它使数据库工具,错误消息,排序次序,日期,时间,货币,数字,和日历自动适应本地化语言和平台。
影响oracle数据库字符集最重要的参数是NLS_LANG参数。它的格式如下:
NLS_LANG = language_territory.charset
它有三个组成部分(语言、地域和字符集),每个成分控制了NLS子集的特性。其中:
Language指定服务器消息的语言,territory 指定服务器的日期和数字格式,charset 指定字符集。
如:AMERICAN _ AMERICA. ZHS16GBK。
从NLS_LANG的组成我们可以看出,真正影响数据库字符集的其实是第三部分。
所以两个数据库之间的字符集只要第三部分一样就可以相互导入导出数据,前面影响的只是提示信息是中文还是英文。
如何查询Oracle的字符集
1、查询oracle server端的字符集
有很多种方法可以查出oracle server端的字符集,比较直观的查询方法是以下这种:
SQL>select userenv('language') from dual;
结果类似如下:AMERICAN _ AMERICA. ZHS16GBK
或:
SQL>select userenv('language') from dual;
USERENV('LANGUAGE')
----------------------------------------------------
SIMPLIFIED CHINESE_CHINA.ZHS16GBK
2、如何查询dmp文件的字符集
用oracle的exp工具导出的dmp文件也包含了字符集信息,dmp文件的第2和第3个字节记录了dmp文件的字符集。
如果dmp文件不大,比如只有几M或几十M,(这里推荐一个工具 Toad 可以使用这个工具打开 dmp文件)
可以用UltraEdit打开(16进制方式,看第2第3个字节的内容,如0354,然后用以下SQL查出它对应的字符集:
SQL> select nls_charset_name(to_number('0354','xxxx')) from dual;
结果是: ZHS16GBK
3、查询oracle client端的字符集
在windows平台下,就是注册表里面HKEY_LOCAL_MACHINESOFTWAREORACLEHOME0NLS_LANG
或 HKEY_LOCAL_MACHINESOFTWAREWow6432NodeORACLEKEY_OraClient10g_home4(我电脑)
如果检查的结果发现server端与client端字符集不一致,请统一修改为同server端相同的字符集。
三、修改oracle的字符集
1、修改server端字符集(我试过的)
若此时数据库服务器已启动,则先执行SHUTDOWN IMMEDIATE命令关闭数据库服务器,然后执行以下命令:
SQL>STARTUP MOUNT;
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 INTERNAL_USE ZHS16GBK(这里为你所要转换成的字符集); //跳过超子集检测
SQL>ALTER DATABASE national CHARACTER SET INTERNAL ZHS16GBK(这里为你所要转换成的字符集);
执行后出错ORA-00933: SQL 命令未正确结束,不过执行上一行命令已经生效,这一句出错对修改似乎没有影响。
下面关闭数据库,然后重新启动:
SQL>SHUTDOWN IMMEDIATE;
SQL>STARTUP
2、修改dmp文件字符集(这个我没有试,借鉴来的)
具体的修改方法比较多,最简单的就是直接用UltraEdit修改dmp文件的第2和第3个字节。
比如想将dmp文件的字符集改为ZHS16GBK,可以用以下SQL查出该种字符集对应的16进制代码:
SQL> select to_char(nls_charset_id('ZHS16GBK'), 'xxxx') from dual;
结果是:0354
然后将dmp文件的2、3字节修改为0354即可。
3. 修改客户端的文件字符集和新配置一个环境变量:
1修改客户端字符集:
运行regedit命令,在注册表中找到这个下的键HKEY_LOCAL_MACHINESOFTWAREORACLEHOME0NLS_LANG
将其值改为上述服务器端你所修改的字符编码值。
2 新配置一个环境变量
点击我的电脑右键》属性》高级》环境变量》新建一个用户变量,其变量名是:NLS_LANG;
变量值是:SIMPLIFIED CHINESE_CHINA.WE8ISO8859P1(这里的字符集和你在服务器端和客户端修改的字符集必须一致)。
注意:最后一定要重启oracle的主服务!不然乱码问题依旧存在.
总结:一般将数据库服务器端,客户端,还有新配置的环境变量的值修改成和dmp文档一样的字符集就可解决乱码的问题!
解决方案:
在导入DMP文件前,在客户端导入与服务器一致的环境变量。
例如:export NLS_LANG=American_America.ZHS16GBK
备注:随笔中内容来源于网上资料整理,仅供参考。