您的位置:首页 > 数据库 > Oracle

字符集问题的初步探讨(四)

2006-09-13 10:30 567 查看
 6. 乱码的生成通常在我们的现实环境中,存在3个字符集设置。第一: 客户端应用字符集(Client Application Character Set)第二: 客户端NLS_LANG参数设置第三: 服务器端,数据库字符集(Character Set)设置 我们说,一个字符在客户端应用(比如SQLPLUS,CMD,NOTEPAD等)中以怎样的字符显示取决于客户端操作系统,客户端能够显示怎样的字符,我们就可以在应用中录入这些字符,至于这些字符能否在数据库中正常存储,就和另外的两个字符集设置紧密相关了。在传输过程中,客户端NLS_LANG主要用于进行转换判断如果NLS_LANG等于数据库字符集,则不进行任何转换直接把字符插入数据库如果不同则进行转换,转换主要有两个任务如果存在对应关系,则把相应二进制编码经过映射后(这一步映射以后,所代表的字符可能发生转换)传递给数据库如果不存在对应关系,则传递一个替换字符(很多平台就是?) 数据库字符集,在和客户端NLS_LANG不同时,会把经过NLS_LANG转换的字符进行进一步处理对于?(即不存在对应关系的字符)直接以?形式存放入数据库对于其他字符,在NLS_LANG和数据库字符集之间进行转换后存入。 以下我们来看一下最为常见的字符集及乱码的产生:1.当NLS_LANG字符集与数据库字符集不同,同时NLS_LANG不同于Server端字符集设置在这种情况下,存在两种可能:客户端输入的字符在NLS_LANG中没有对应的字符,这时无法转换,NLS_LANG使用替换字符替代这些无法映射的字符(这一步转换在TTS中完成),在很多字符集中这个替代字符就是”?”当客户端的字符在NLS_LANG中对应了不同的字符时,传递给数据库以后发生转换,存储的是字符,但是已经丢失了元数据,数据库中的字符不再代表客户端的输入。而且这个过程不可逆,这也就是为什么很多时候在客户端输入的是正常的编码,查询之后会得到未知字符的原因。我们通过上图来简单说明一下这个过程,当客户端在WE8ISO8859P15字符集时,输入欧元符号: €,这时客户端NLS_LANG和数据库端字符集不同,进行第一次转换,客户端€符号编码是A4,在NLS_LANG转换时,A4对应了NLS_LANG中的‘¤’,这一步的转换产生了错误映射。由于数据库字符集不同于NLS_LANG设置,这时进一步的转换发生了,存入数据库的编码变成了C2A4,虽然同NLS_LANG进行了正确的转换,但是客户端录入的数据已经损坏或者丢失了。我们可以用我们熟悉的字符集做一个简单的测试:测试环境:客户端应用为中文18030字符集NLS_LANG设置为US7ASCII字符集数据库CHARACTER SET为ZHS16GBK c:>set NLS_LANG=AMERICAN_AMERICA.US7ASCIIc:>sqlplus eygle/eygleSQL*Plus: Release 9.2.0.4.0 - Production on Tue Nov 4 01:19:57 2003Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.Connected to:Oracle9i Enterprise Edition Release 9.2.0.4.0 - ProductionWith the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining optionsJServer Release 9.2.0.4.0 - ProductionSQL> insert into test values('测试');1 row created.SQL> select name,dump(name) from test;NAME    DUMP(NAME)--------------------------------------------------2bJT    Typ=1 Len=4: 50,98,74,84这时候我们发现,查询出来的是混乱的字符,我们把这些字符转换为2进制就是110010   1100010   1001010   1010100补全8位就是       00110010  01100010  01001010  01010100我们把首位换成1   10110010  11100010  11001010  11010100我们来看正确的存储:c:>set nls_lang=AMERICAN_AMERICA.ZHS16GBKc:>sqlplus eygle/eygleSQL*Plus: Release 9.2.0.4.0 - Production on Tue Nov 4 01:40:18 2003Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.Connected to:Oracle9i Enterprise Edition Release 9.2.0.4.0 - ProductionWith the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining optionsJServer Release 9.2.0.4.0 - ProductionSQL> insert into test values('测试');1 row created.SQL> col dump(name) for a30SQL> select name,dump(name) from test;NAME DUMP(NAME)---------- ------------------------------测试 Typ=1 Len=4: 178,226,202,2121 row selected.我们把这个结果转换为2进制表示          10110010  11100010  11001010  11010100这个结果正是我们前面乱码首位补全1后的结果。这个测试说明在US7ASCII转换中文的时候除去了首位的 1,这样就丢失了元数据,导致乱码出现,NLS_LANG的转换作用由此可加一斑! 3. NLS_LANG和数据库字符集相同时在这种情况下,数据库端对客户端传递过来的编码不进行任何转换(这样可以提高性能),直接存储进入数据库,那么这时候就存在和上面同样的问题,如果客户端传递过来的字符集在数据库中有正确的对应就可以正确存储,如果没有,就会被替换字符置换成?,乱码就这样产生了。如上图所示,当NLS_LANG和数据库字符集设置相同都为UTF8时,客户端的欧元符号的编码A4就不会经过任何转换就插入到数据库中,而在UTF8的数据库中,A4代表的是一个非法字符。 我们来看一个简单的测试测试环境:客户端字符集应用为中文GB18030客户端NLS_LANG为US7ASCII数据库字符集为US7ASCII我们知道这个时候,存入的数据,数据库不进行任何转换,在以下的测试中,我们看到中文在US7ASCII字符集下得以正确显示。 c:>set nls_lang=AMERICAN_AMERICA.US7ASCIIc:>sqlplus eygle/eygleSQL*Plus: Release 9.2.0.4.0 - Production on Tue Nov 4 01:02:04 2003Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.Connected to:Oracle9i Enterprise Edition Release 9.2.0.4.0 - ProductionWith the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining optionsJServer Release 9.2.0.4.0 - ProductionSQL> insert into test values('测试');1 row created.SQL> commit;Commit complete.SQL> select * from test;NAME----------测试1 row selected.SQL> col dump(name) for a30SQL> select name,dump(name) from test;NAME       DUMP(NAME)---------- ------------------------------测试       Typ=1 Len=4: 178,226,202,2121 row selected.SQL> select * from nls_database_parameters;PARAMETER                      VALUE------------------------------ ----------------------------------------NLS_LANGUAGE                   AMERICANNLS_TERRITORY                  AMERICANLS_CURRENCY                   $NLS_ISO_CURRENCY               AMERICANLS_NUMERIC_CHARACTERS         .,NLS_CHARACTERSET               US7ASCIINLS_CALENDAR                   GREGORIANNLS_DATE_FORMAT                DD-MON-RRNLS_DATE_LANGUAGE              AMERICANNLS_SORT                       BINARYNLS_TIME_FORMAT                HH.MI.SSXFF AMPARAMETER                      VALUE------------------------------ ----------------------------------------NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AMNLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZRNLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZRNLS_DUAL_CURRENCY              $NLS_COMP                       BINARYNLS_LENGTH_SEMANTICS           BYTENLS_NCHAR_CONV_EXCP            FALSENLS_NCHAR_CHARACTERSET         AL16UTF16NLS_RDBMS_VERSION              9.2.0.4.020 rows selected.SQL> 结语:对于DBA来说,有一个很重要的原则就是:不要把你的数据库置于危险的境地!这就要求我们,在进行任何可能对数据库结构发生改变的操作之前,先做有效的备份,很多DBA没有备份的操作中得到了惨痛的教训。 7. 关于字符集更改的内部操作 前面我们提到,通过修改props$的方式更改字符集在Oracle7之后是一种极其危险的方式,应该尽量避免。我们又知道,通过ALTER DATABASE CHARACTER SET更改字符集虽然安全可靠,但是有严格的子集和超集的约束,实际上我们很少能够用到这种方法。实际上Oracle还存在另外一种更改字符集的方式.如果你注意过的话,在Oracle的alert<sid>.log文件中,你可能看到过这样的日志信息:alter database character set INTERNAL_CONVERT ZHS16GBKUpdating character set in controlfile to ZHS16GBK SYS.SNAP$ (REL_QUERY) - CLOB representation altered SYS.METASTYLESHEET (STYLESHEET) - CLOB representation altered SYS.EXTERNAL_TAB$ (PARAM_CLOB) - CLOB representation altered XDB.XDB$RESOURCE (SYS_NC00027$) - CLOB representation altered ODM.ODM_PMML_DTD (DTD) - CLOB representation altered OE.WAREHOUSES (SYS_NC00003$) - CLOB representation altered PM.ONLINE_MEDIA (SYS_NC00042$) - CLOB representation altered PM.ONLINE_MEDIA (SYS_NC00062$) - CLOB representation altered PM.ONLINE_MEDIA (PRODUCT_TEXT) - CLOB representation altered PM.ONLINE_MEDIA (SYS_NC00080$) - CLOB representation altered PM.PRINT_MEDIA (AD_SOURCETEXT) - CLOB representation altered PM.PRINT_MEDIA (AD_FINALTEXT) - CLOB representation alteredCompleted: alter database character set INTERNAL_CONVERT ZHS1 在这里面,我们看到这样一条重要的,Oracle非公开的命令: alter database character set INTERNAL_CONVERT/ INTERNAL_USE ZHS16GBK 这个命令是当你选择了使用典型方式创建了种子数据库以后,Oracle会根据你选择的字符集设置,把当前种子数据库的字符集更改为期望字符集,这就是这条命令的作用.在使用这个命令时,Oracle会跳过所有子集及超集的检查,在任意字符集之间进行强制转换,所以,使用这个命令时你必须十分小心,你必须清楚这一操作会带来的风险.我们之前讲过的内容仍然有效,你可以使用csscan扫描整个数据库,如果在转换的字符集之间确认没有严重的数据损坏,或者你可以使用有效的方式更改,你就可以使用这种方式进行转换.我们来看一下具体的操作过程及Oracle的内部操作: SQL> shutdown immediateDatabase closed.Database dismounted.ORACLE instance shut down.SQL> startup mountORACLE instance started.Total System Global Area  135337420 bytesFixed Size                   452044 bytesVariable Size             109051904 bytesDatabase Buffers           25165824 bytesRedo Buffers                 667648 bytesDatabase mounted.SQL> ALTER SYSTEM ENABLE RESTRICTED SESSION;System altered.SQL> ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0;System altered.SQL> ALTER SYSTEM SET AQ_TM_PROCESSES=0;System altered.SQL> ALTER DATABASE OPEN;Database altered.SQL> alter session set events '10046 trace name context forever,level 12';Session altered.SQL> alter database character set INTERNAL_USE ZHS16CGB231280Database altered.SQL> 这是alert.log文件中的记录信息:
 格式化10046跟踪文件,得到以下信息(摘要): alter session set events '10046 trace name context forever,level 12'alter database character set INTERNAL_USE ZHS16CGB231280call     count       cpu    elapsed       disk      query    current        rows------- ------  -------- ---------- ---------- ---------- ----------  ----------Parse        1      0.00       0.00          0          0          0           0Execute      1      4.88       6.04        910      16825      18099           0Fetch        0      0.00       0.00          0          0          0           0------- ------  -------- ---------- ---------- ---------- ----------  ----------total        2      4.88       6.04        910      16825      18099           0Misses in library cache during parse: 1Optimizer goal: CHOOSEParsing user id: SYSElapsed times include waiting on following events:  Event waited on                             Times   Max. Wait  Total Waited  ----------------------------------------   Waited  ----------  ------------  control file sequential read                    4        0.00          0.00  control file parallel write                     2        0.05          0.08  log file sync                                   2        0.08          0.08  SQL*Net message to client                       1        0.00          0.00  SQL*Net message from client                     1       18.06         18.06********************************************************************************....update col$ set charsetid = :1 where charsetform = :2....update argument$ set charsetid = :1 where charsetform = :2....update collection$ set charsetid = :1 where charsetform = :2....update attribute$ set charsetid = :1 where charsetform = :2....update parameter$ set charsetid = :1 where charsetform = :2....update result$ set charsetid = :1 where charsetform = :2....update partcol$ set spare1 = :1 where charsetform = :2....update subpartcol$ set spare1 = :1 where charsetform = :2....update props$ set value$ = :1 where name = :2....update "SYS"."KOTAD$" set SYS_NC_ROWINFO$ = :1 where SYS_NC_OID$ = :2....update seq$ set increment$=:2,minvalue=:3,maxvalue=:4,cycle#=:5,order$=:6,  cache=:7,highwater=:8,audit$=:9,flags=:10 where obj#=:1....update kopm$ set metadata = :1,  length   = :2 where name='DB_FDO'....ALTER DATABASE CLOSE NORMAL 此处生成的日志你可以在这里下载(供参考):http://www.eygle.com/special/primary_ora_13730.ziphttp://www.eygle.com/special/primary_ora_13730.tkf.log我们看到这个过程和之前ALTER DATABASE CHARACTER SET操作的内部过程是完全相同的,也就是说INTERNAL_USE提供的帮助就是使Oracle数据库绕过了子集与超集的校验.这一方法在某些方面是有用处的,比如测试;应用于产品环境大家应该格外小心,除了你以外,没有人会为此带来的后果负责:结语(我们不妨再说一次):对于DBA来说,有一个很重要的原则就是:不要把你的数据库置于危险的境地!这就要求我们,在进行任何可能对数据库结构发生改变的操作之前,先做有效的备份,很多DBA没有备份的操作中得到了惨痛的教训。
                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息