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

link:

http://www.eygle.com/special/NLS_CHARACTER_SET_03.htm

2. 字符集的更改

数据库创建以后,如果需要修改字符集,通常需要重建数据库,通过导入导出的方式来转换。
我们也可以通过以下方式更改

> >
> > > > ALTER DATABASE CHARACTER SET >
>
>
> >


注意:修改数据库字符集时必须谨慎,修改之前一定要为数据库备份。由于不能回退这项操作,因此可能会造成数据丢失或者损坏。

这是最简单的转换字符集的方式,但并不总是有效。
这个命令在Oracle8时被引入Oracle,这个操作在本质上并不转换任何数据库字符,只是简单的更新数据库中所有跟字符集相关的信息。

这意味着,你只能在新字符集是旧字符集严格超集的情况下使用这种方式转换。
所谓超集是指:
当前字符集中的每一个字符在新字符集中都可以表示,并使用同样的代码点
比如很多字符集都是US7ASCII的严格超集。

如果不是超集,将获得以下错误:

> >
> > SQL> ALTER DATABASE CHARACTER SET ZHS16CGB231280;
> > ALTER DATABASE CHARACTER SET ZHS16CGB231280
> > *
> > ERROR at line 1:
> > ORA-12712: new character set must be a superset of old character set


下面我们来看一个测试(以下测试在Oracle9.2.0下进行,Oracle9i较Oracle8i在编码方面有较大改变,在Oracle8i中,测试结果可能略有不同):

> > SQL> select name,value$ from props$ where name like '%NLS%'; >
> NAME VALUE$ > ------------------------------ ------------------------------ > NLS_LANGUAGE AMERICAN > NLS_TERRITORY AMERICA > NLS_CURRENCY $ > NLS_ISO_CURRENCY AMERICA > NLS_NUMERIC_CHARACTERS ., > NLS_CHARACTERSET US7ASCII > NLS_CALENDAR GREGORIAN > NLS_DATE_FORMAT DD-MON-RR > NLS_DATE_LANGUAGE AMERICAN > ………………. > NLS_NCHAR_CHARACTERSET AL16UTF16 > NLS_RDBMS_VERSION 9.2.0.4.0 >
> 20 rows selected. > SQL> select name,dump(name) from eygle.test; >
> NAME DUMP(NAME) > ------------------------------------------------------ > 测试 Typ=1 Len=4: 178,226,202,212 > Test Typ=1 Len=4: 116,101,115,116 >
>
> 2 rows selected.
> >


转换字符集,数据库应该在RESTRICTED模式下进行.

> > c:>sqlplus "/ as sysdba" >
> SQL*Plus: Release 9.2.0.4.0 - Production on Sat Nov 1 10:52:30 2003 >
> Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved. >
>
> Connected to: > Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production > With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining options > JServer Release 9.2.0.4.0 - Production >
> SQL> shutdown immediate > Database closed. > Database dismounted. > ORACLE instance shut down. > SQL> STARTUP MOUNT; > ORACLE instance started. >
> Total System Global Area 76619308 bytes > Fixed Size 454188 bytes > Variable Size 58720256 bytes > Database Buffers 16777216 bytes > Redo Buffers 667648 bytes > Database mounted. > SQL> ALTER SESSION SET SQL_TRACE=TRUE; >
> Session altered. >
> 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> set linesize 120 > SQL> ALTER DATABASE CHARACTER SET ZHS16GBK; > ALTER DATABASE CHARACTER SET ZHS16GBK > * > ERROR at line 1: > ORA-12721: operation cannot execute when other sessions are active >
>
> SQL> ALTER DATABASE CHARACTER SET ZHS16GBK; > ALTER DATABASE CHARACTER SET ZHS16GBK > * > ERROR at line 1: > ORA-12716: Cannot ALTER DATABASE CHARACTER SET when CLOB data exists > 在Oracle9i中,如果数据库存在CLOB类型字段,那么就不允许对字符集进行转换 >
> SQL> > >  


这时候,我们可以去查看alert

  1<sid>.log日志文件,看CLOB字段存在于哪些表上: 
  2
  3&gt; 
  4&gt;       
  5&gt;     &gt; ALTER DATABASE CHARACTER SET ZHS16GBK  
  6&gt;     &gt;  **SYS.METASTYLESHEET (STYLESHEET) - CLOB populated**  
  7&gt;     &gt;  ORA-12716 signalled during: ALTER DATABASE CHARACTER SET ZHS16GBK...  
  8  
  9---  
 10  
 11对于不同情况,Oracle提供不同的解决方案,如果是用户数据表,一般我们可以把包含CLOB字段的表导出,然后drop掉相关对象,   
 12转换后再导入数据库;对于系统表,可以按照以下方式处理: 
 13
 14&gt; 
 15&gt;     SQL&gt; truncate table Metastylesheet;
 16&gt; 
 17&gt; Table truncated. 
 18&gt;     
 19&gt;     
 20&gt;       
 21&gt;     &gt;   
 22  
 23---  
 24  
 25然后可以继续进行转换! 
 26
 27&gt; 
 28&gt;     ****
 29&gt;     **SQL &gt; ALTER SESSION SET SQL_TRACE=TRUE;**
 30&gt;     
 31&gt;     Session altered.
 32&gt;     
 33&gt;     SQL&gt; ALTER DATABASE CHARACTER SET ZHS16GBK;
 34&gt;     
 35&gt;     Database altered.
 36&gt;     
 37&gt;     SQL&gt; ALTER SESSION SET SQL_TRACE=FALSE;
 38&gt;     
 39&gt;     Session altered.
 40&gt;     				
 41&gt;     				  
 42  
 43---  
 44  
 45在9.2.0中,转换完成以后,可以通过运行catmet.sql脚本来重建Metastylesheet表: 
 46
 47&gt; 
 48&gt;       
 49&gt;     &gt; 
 50&gt; 
 51&gt; SQL&gt; @?/rdbms/admin/catmet.sql   
 52  
 53---  
 54  
 55转换后的数据: 
 56
 57&gt; 
 58&gt;       
 59&gt;     &gt; 
 60&gt;     SQL&gt; select name,value$ from props$ where name like '%NLS%';
 61&gt;     
 62&gt;     NAME                           VALUE$
 63&gt;     ------------------------------ ------------------------------
 64&gt;     NLS_LANGUAGE                   AMERICAN
 65&gt;     NLS_TERRITORY                  AMERICA
 66&gt;     NLS_CURRENCY                   $
 67&gt;     NLS_ISO_CURRENCY               AMERICA
 68&gt;     NLS_NUMERIC_CHARACTERS         .,
 69&gt;     NLS_CHARACTERSET               ZHS16GBK
 70&gt;     …..
 71&gt;     NLS_NCHAR_CHARACTERSET         AL16UTF16
 72&gt;     NLS_RDBMS_VERSION              9.2.0.4.0
 73&gt;     
 74&gt;     20 rows selected.
 75&gt;     
 76&gt;     SQL&gt; select * from eygle.test;
 77&gt;     
 78&gt;     NAME
 79&gt;     ------------------------------
 80&gt;     测试
 81&gt;     test
 82&gt;     
 83&gt;     2 rows selected.
 84&gt;     			  
 85  
 86---  
 87  
 88提示:    
 89**通过设置sql_trace,我们可以跟踪很多数据库的后台操作,这个工具是DBA常用的“利器”之一。**   
 90我们简单看一下数据库更改字符集时的后台处理,我提取了主要的更新部分。   
 91通过以下跟踪过程,我们看到数据库在更改字符集的时候,主要更新了12张数据字典表,修改了数据库的原数据,这也证实了我们以前的说法:   
 92这个更改字符集的操作在本质上并不转换任何数据库字符,只是简单的更新数据库中所有跟字符集相关的信息。   
 93
 94
 95&gt; 
 96&gt;     update col$ set charsetid = :1 
 97&gt;     where
 98&gt;      charsetform = :2
 99&gt;     
100&gt;     
101&gt;     update argument$ set charsetid = :1 
102&gt;     where
103&gt;      charsetform = :2
104&gt;     
105&gt;     
106&gt;     update collection$ set charsetid = :1 
107&gt;     where
108&gt;      charsetform = :2
109&gt;     
110&gt;     
111&gt;     update attribute$ set charsetid = :1 
112&gt;     where
113&gt;      charsetform = :2
114&gt;     
115&gt;     
116&gt;     update parameter$ set charsetid = :1 
117&gt;     where
118&gt;      charsetform = :2
119&gt;     
120&gt;     
121&gt;     update result$ set charsetid = :1 
122&gt;     where
123&gt;      charsetform = :2
124&gt;     
125&gt;     
126&gt;     update partcol$ set spare1 = :1 
127&gt;     where
128&gt;      charsetform = :2
129&gt;     
130&gt;     
131&gt;     update subpartcol$ set spare1 = :1 
132&gt;     where
133&gt;      charsetform = :2
134&gt;     
135&gt;     
136&gt;     **update props$ set value$ = :1 
137&gt;     where
138&gt;      name = :2**
139&gt;     
140&gt;     
141&gt;     update "SYS"."KOTAD$" set SYS_NC_ROWINFO$ = :1 
142&gt;     where
143&gt;      SYS_NC_OID$ = :2
144&gt;     
145&gt;     update seq$ set increment$=:2,minvalue=:3,maxvalue=:4,cycle#=:5,order$=:6,
146&gt;       cache=:7,highwater=:8,audit$=:9,flags=:10 
147&gt;     where
148&gt;      obj#=:1
149&gt;     
150&gt;     update kopm$ set metadata = :1, length  = :2 
151&gt;     where
152&gt;      name='DB_FDO'
153&gt;     				
154&gt;     				  
155  
156---  
157  
158在这里我们顺便纠正一个由来以及的错误方法.   
159经常可以在网上看到这样的更改字符集的方法: 
160
161&gt; 
162&gt;     
163&gt;     1)用SYS用户名登陆ORACLE。
164&gt; 
165&gt; 2)查看字符集内容 
166&gt; 
167&gt; SQL&gt;SELECT * FROM PROPS$; 
168&gt; 
169&gt; 3)修改字符集 
170&gt; 
171&gt; SQL&gt; update props$ set value$='新字符集' where name='NLS_CHARACTERSET' 
172&gt; 
173&gt; 4) COMMIT;   
174  
175---  
176  
177  
178我们看到很多人在这个问题上遇到了惨痛的教训,使用这种方式更改字符集,如果你的value$值输入了不正确的字符集,在8i中那么你   
179的数据库可能会无法启动,这种情况是非常严重的,有时候你必须从备份中进行恢复;如果是在9i中,可以重新启动数据库后再修改回正   
180确的字符集。但是我们仍然不建议使用这种方式进行任何数据库修改,这是一种极其危险的操作。   
181实际上当我们更新了字符集,数据库启动时会根据数据库的字符集自动的来修改控制文件的字符集,如果字符集可以识别,更新控制文   
182件字符集等于数据库字符集;如果字符集不可识别,那么控制文件字符集更新为US7ASCII. 
183
184通过更新props$表的方式修改字符集,在Oracle7之后就不应该被使用. 
185
186以下是我的测试结果,但是严禁一切不备份的修改研究,即使是对测试库的。    
187
188
189&gt; 
190&gt;     SQL&gt; update props$ set value$='EYGLE' where name='NLS_CHARACTERSET';
191&gt;     
192&gt;     1 row updated.
193&gt;     
194&gt;     SQL&gt; commit;
195&gt;     
196&gt;     Commit complete.
197&gt;     
198&gt;     SQL&gt; select name,value$ from props$ where name like '%NLS%';
199&gt;     
200&gt;     NAME                           VALUE$
201&gt;     ------------------------------ -----------------------------------
202&gt;     NLS_LANGUAGE                   AMERICAN
203&gt;     NLS_TERRITORY                  AMERICA
204&gt;     NLS_CURRENCY                   $
205&gt;     NLS_ISO_CURRENCY               AMERICA
206&gt;     NLS_NUMERIC_CHARACTERS         .,
207&gt;     NLS_CHARACTERSET               EYGLE
208&gt;     NLS_CALENDAR                   GREGORIAN
209&gt;     NLS_DATE_FORMAT                DD-MON-RR
210&gt;     NLS_DATE_LANGUAGE              AMERICAN
211&gt;     ….
212&gt;     NLS_NCHAR_CHARACTERSET         ZHS16GBK
213&gt;     NLS_RDBMS_VERSION              8.1.7.1.1
214&gt;     
215&gt;     18 rows selected.
216&gt;     
217&gt;     重新启动数据库,发现alert.log文件中记录如下操作:
218&gt;     
219&gt;     Mon Nov 03 16:11:35 2003
220&gt;     Updating character set in controlfile to US7ASCII
221&gt;     Completed: ALTER DATABASE OPEN
222&gt;     
223&gt;     启动数据库后恢复字符集设置:
224&gt;     
225&gt;     SQL&gt; update props$ set value$='ZHS16GBK' where name='NLS_CHARACTERSET';
226&gt;     
227&gt;     1 row updated.
228&gt;     
229&gt;     SQL&gt; commit;
230&gt;     
231&gt;     Commit complete.
232&gt;     
233&gt;     SQL&gt; select name,value$ from props$ where name like '%NLS%';
234&gt;     
235&gt;     NAME                           VALUE$
236&gt;     ------------------------------ -----------------------------------
237&gt;     NLS_LANGUAGE                   AMERICAN
238&gt;     NLS_TERRITORY                  AMERICA
239&gt;     NLS_CURRENCY                   $
240&gt;     NLS_ISO_CURRENCY               AMERICA
241&gt;     NLS_NUMERIC_CHARACTERS         .,
242&gt;     NLS_CHARACTERSET               ZHS16GBK
243&gt;     NLS_CALENDAR                   GREGORIAN
244&gt;     NLS_DATE_FORMAT                DD-MON-RR
245&gt;     NLS_DATE_LANGUAGE              AMERICAN
246&gt;     ………
247&gt;     NLS_COMP                       BINARY
248&gt;     NLS_NCHAR_CHARACTERSET         ZHS16GBK
249&gt;     NLS_RDBMS_VERSION              8.1.7.1.1
250&gt;     
251&gt;     18 rows selected.
252&gt;     
253&gt;     重新启动数据库后,发现控制文件的字符集被更新:
254&gt;     
255&gt;     Mon Nov 03 16:21:41 2003
256&gt;     Updating character set in controlfile to ZHS16GBK
257&gt;     Completed: ALTER DATABASE OPEN
258&gt;     
259&gt;        
260  
261---  
262  
263理解了字符集调整的内部操作以后,我们可以轻易的指出,以上的方法是不正确的,通过前面 ” ALTER DATABASE CHARACTER SET” 方式更改字   
264符集时,Oracle至少需要更改12张数据字典表,而这种直接更新props$表的方式只完成了其中十二分之一的工作,潜在的完整性隐患是可想而知的。 
265
266** 所以,更改字符集尽量要使用正常的途径  ** 。</sid>
Published At
Categories with 数据库类
Tagged with
comments powered by Disqus