一、背景
在我们日常的测试工作中,无论在数据迁移还是系统测试过程中,测试数据准确性和可用性是测试工作的重中之重,测试人员经常需要查询数据库中的数据或手工造测试数据,而登到oracle数据库后有时会发现显示中文乱码的情况,如下图所示:
或者是:
好好的怎么就乱码了呢?明明“之前”访问其他数据库是没问题。在学习和查询相关资料后我解决了这个问题,但由此引发了我对出现汉字乱码问题的学习兴趣,都是什么原因导致的汉字显示乱码呢?
首先,字符集设置不当是影响Oracle数据库汉字显示的关键问题。下文则详细介绍了oracle关于字符集的分类、构成及设定方法,分析了ORACLE数据库汉字显示乱码的常见现象及原因,并针对各种现象及原因提出了行之有效的解决办法。
二、关于字符集
字符集是ORACLE为适应不同语言文字显示而设定的。用于汉字显示的字符集主要有ZHS16CGB231280、ZHS16GBK、US7ASCII和UTF-8等。字符集同时存在于服务器端和客户端。服务器端字符集是在安装ORACLE时指定的,字符集登记信息存储在ORACLE数据库字典的V$NLS_PARAMETERS表中;而客户端字符集是在系统注册表(WINDOWS系统)或在用户的环境变量(UNIX系统)中设定的。
三、字符集的构成与设定
字符集的构成与设定方式分为客户端与服务器端两种:
(1)客户端字符集的构成与设定
客户端的字符集是由当前用户的环境变量NLS_LANG设定的。环境变量NLS_LANG的构成:NLS_LANG=language_territory.charset
其中:
language 指定服务器消息的语言 territory 指定服务器的日期和数字格式 charset 指定字符集
三个成分可以任意组合,例如:
AMERICAN_AMERICA.US7SCII SIMPLIFIED CHINESE_CHINA.ZHS16GBK AMERICAN_AMERICA.ZHS16GBK
WINDOWS系统是在注册表项:HKEY_LOCAL_MACHINE/SOFTWARE/ORACLE/HOME0/NLS_LANG中设定;
(2)服务端字符集的构成与设定
服务端字符集的构成体现在数据字典表V$NLS_PARAMETERS的NLS_LANGUAGE、NLS_TERRITORY、NLS_CHARACTERSET三项取值上,其中NLS_CHARACTERSET的取值就是具体的数据库字符集。如利用查询语句SQL>SELECT * FROM V$NLS_PARAMETERS;可得到如下结果:
PARAMETER VALUE NLS_LANGUAGE SIMPLIFIED CHINESE NLS_TERRITORY CHINA NLS_CHARACTERSET ZHS16GBK
即当前数据库使用的字符集是ZHS16GBK。
数据库服务端的字符集是在创建数据时设定的。但可通过如下方法对已设定的字符集进行修改:
方法一:重建数据库。建立数据库时将数据库的字符集设定为所需字符集。
方法二:修改SYS.PROPS$表。即用SYS用户登陆ORACLE后,利用下面语句修改相应的字符集并提交:SQL>UPDATE PROPS$ SET VALUE$=‘ZHS16GBK’WHERE NAME=‘NLS_CHARACTERSET’;SQL>COMMIT;
通过此种方法来更改数据库字符集,只对更改后的数据有效,即数据库中原来的数据仍以原字符集被存储。
方法三:在linux系统下,则需要手动替换整个字符集,具体操作如下:
1.查看当前数据库字符集
select * from nls_database_parameters where parameter='NLS_CHARACTERSET'; ----------------------------------- PARAMETER VALUE NLS_CHARACTERSET WE8ISO8859P1
2.将字符集修改为中文字符集ZHS16GBK(具体linux版本不同可能存在差异)
SQL> shutdown immediate Database closed. Database dismounted. ORACLE instance shut down. SQL> STARTUP MOUNT; ORACLE instance started. Total System Global Area 734003200 bytes Fixed Size 1221564 bytes Variable Size 264244292 bytes Database Buffers 465567744 bytes Redo Buffers 2969600 bytes Database mounted. SQL> ALTER SYSTEM ENABLE RESTRICTED SESSION; System altered. SQL> ALTER DATABASE OPEN; Database altered. SQL> ALTER DATABASE CHARACTER SET ZHS16GBK; ALTER DATABASE CHARACTER SET ZHS16GBK * ERROR at line 1: ORA-12712: new character set must be a superset of old character set SQL> ALTER DATABASE character set INTERNAL_USE ZHS16GBK; Database altered. SQL> SHUTDOWN IMMEDIATE; Database closed. Database dismounted. ORACLE instance shut down. SQL> startup ORACLE instance started. Total System Global Area 734003200 bytes Fixed Size 1221564 bytes Variable Size 264244292 bytes Database Buffers 465567744 bytes Redo Buffers 2969600 bytes Database mounted. Database opened.
此种方法依旧只对更改后的数据有效,即数据库中原来的数据仍以原字符集被存储。
另外,有的还利用CREATE DATABASE CHARACTER SET ZHS16GBK命令暂时的修改字符集,当重启数据库后,数据库字符集将恢复原来的字符集。
四、 查看数据库字符集
这涉及三方面的字符集:
一是:oracle server端的字符集;
二是:oracle client端的字符集;
三是:dmp文件的字符集;
在做数据导入的时候,需要这三个字符集都一致才能正确导入。
2.1查询oracle server端的字符集
有很多种方法可以查出oracle server端的字符集
①比较直观的查询方法
SQL> select userenv('language') from dual;
查询结果如下: SIMPLIFIED CHINESE_CHINA.ZHS32GB18030
②查看有关字符集的所有参数
SELECT * FROM v$nls_parameters;
结果中的NLS_CHARACTERSET ZHS32GB18030
2.2查看dmp文件的字符集
用oracle的exp工具导出的dmp文件也包含了字符集信息,dmp文件的第2和第3个字节记录了dmp文件的字符集。如果dmp文件不大,比 如只有几M或几十M,可以用UltraEdit打开(16进制方式),看第2第3个字节的内容,如0354,然后用以下SQL查出它对应的字符集:
SQL> select nls_charset_name(to_number('0354','xxxx')) from dual;ZHS16GBK
2.3查询oracle client端的字符集
在windows平台下,就是注册表里面相应OracleHome的NLS_LANG。具体路径:
Win+R中输入regedit 路径\HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\ nls_lang 显示本地的字符集,这样就只影响这个窗口里面的环境变量。
在unix平台下,就是环境变量NLS_LANG。
$echo $NLS_LANG
AMERICAN_AMERICA.ZHS16GBK
以我行常用的PLSQL Developer为例,在不同oracle版本下的客户端环境变量配置地址不同:
1.oracle10g注册表说在位置如下:
①在windows+R键中输入regedit
②\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\ORACLE\KEY_OraDb10g_home1
③修改NLS_LANG=“SIMPLIFIED Chinese_CHINA.ZHS16GBK”或
NLS_LANG=“SIMPLIFIED CHINESE_CHINA.AL32UTF8”等(如图一)
2.oracle12g注册表位置:HKEY_LOCAL_MACHINE/SOFTWARE/ORACLE/KEY_OraDB12Home1/NLS_LANG
3.oracle11g注册表位置:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\
4.也有注册表位置:
在HKEY_LOCAL_MACHINE/SOFTWARE/ORACLE/Home0/NLS_LANG的其他oracle版本。
如果检查的结果发现server端与client端字符集不一致,建议统一修改为同server端相同的字符集。
五、常见的汉字乱码问题及解决方案(解决方案操作详见三)
要在客户端正确显示ORACLE数据库中的汉字信息,首先必须使客户端的字符集与服务器端的字符集一致;其次是加载到ORACLE数据库的数据字符集必须与服务器字符集一致。据此,汉字显示乱码的问题大致可以分为以下几种情况:
(1)客户端字符集与服务器端字符集不同,服务器端字符集与加载数据字符集一致。这种情况是最常见的,只要把客户端的字符集设置正确即可。具体解决方案:
第一步:查询V$NLS_PARAMETERS得到服务端的字符集:SQL>SELECT * FROM V$NLS_PARAMETERS;
PARAMETER VALUE NLS_LANGUAGE SIMPLIFIED CHINESE NLS_TERRITORY CHINA NLS_CHARACTERSET ZHS16GBK
第二步:根据服务端的字符集设定客户端的字符集,设定方法参见客户端的字符集的设定方式。以UNIX系统为例,可在当前用户的profile文件中增加如下两行:
NLS_LANG=SIMPLIFIED Chinese_CHINA.ZHS16GBK export NLS_LANG
(2)客户端字符集与服务器端字符集相同,服务器端字符集与加载数据字符集不一致。这种情况一般发生在ORACLE版本升级或重新安装数据库时选择了与原来数据库不同的字符集,而恢复加载的备份数据仍是按原字符集卸出的场合。另一种情况是加载从其它使用不同字符集的ORACLE数据库卸出的数据。在这两种情况中,不管客户端字符集与服务器端字符集是否一致都无法正确显示汉字。具体解决方案:
方案一:按服务端字符集的修改方法修改服务端字符集与加载数据字符集一致,然后导入数据。(见三中方法三)
方案二:利用数据格式转储,避开字符集带来的问题。即先将加载数据倒入到与其字符集一致的数据库中,然后再将数据要么按文本格式导出(数据量较小的情况下),要么通过第三方工具(如POWER BUILDER,ACCESS,FOXPRO等)导出数据,最后将导出的数据导入到目标数据库中。
(3)客户端字符集与服务器端字符集不同,服务端字符集与输入数据字符集不同。这种情况是在客户端字符集与服务器端字符集不一致时,从客户端输入了汉字信息。输入的这些信息即便是把客户端字符集更改正确,也无法显示汉字。解决方案:修改客户端字符集与服务端字符集一致后,重新输入数据。(此处较为复杂详情见百度文档)
六、 总结
由于ORACLE数据库创建后,数据库的字符集不能改变,因此需要提前考虑数据库将选用哪一种字符集,在我行中可以在ITA资源申请时关注相关情况。数据库字符集选择的一般规则是将数据库字符集设定为操作系统本地字符集的一个超集,同时数据库字符集也应该是所有客户字符集的超集。在中文环境中我们更多使用ZHS16GBK字符集,因为它包含了ZHS16CGB231280字符集,小伙伴们你们学会了么?
作者:林宫利