Oracle 字符集 原理及問題解決 (全)

 

Oracle全球支持(Globalization Support)允許我們使用本地語言和格式來存儲和檢索數據。通過全球支持,Oracle可以支持多種語言及字符集,得以展示數據庫的強大魅力。

由於不同語言及字符集的共同存儲存在設置上具有一定的複雜性,字符集一度成爲普遍困擾大家的一個主要問題。

本文試圖就一些常見問題進行探討,希望可以把一些實際經驗共享給大家!

1 字符集的基本知識

如果從頭說起,字符集最早的編碼方案來自於與ASCII.這也是我們最常見的編碼方式。該方案起源於1960年代初期,最初是美國國會圖書館制定用來作爲美國圖書館界書目交換的共同標準,最後完善成爲美國的國家標準ASCIIAmerican Standard Codefor Information Interchange),之後進一步演變成世界性的計算機字符編碼標準ISO646(其全名爲7-bit coded character set for information interchange)。成爲計算機編碼方案的基礎。

Oracle數據庫最早支持的編碼方案也就是US7ASCII.

但是我們知道,英文字符一般是以一個字節來存儲的,7位的編碼方案最多隻能代表128個字符;經過擴展的8位的編碼方案也只能代表256個字符,這遠遠不能滿足計算機發展的需要,對於亞洲國家複雜的字符存儲需要更多的碼位,於是各種編碼方案隨之而生。

爲了容納全世界各種語言的所有字符和符號,解決不同編碼之間的兼容和轉換問題,1991年元月,10多家公司共同出資,組建Unicode協會,隨後Unicode編碼產生了。

Unicode協會的口號是: 給每個字符提供了一個唯一的數字,不論是什麼平臺,不論是什麼程序,不論什麼語言。

最初Unicode編碼使用2-Byte(16bit)來進行編碼,但是最多隻能容納65536個字符,仍然不夠使用,後來進行了擴充,也就是Unicode3.1標準,增加了額外的補充字符定義,現在Unicode4.0標準已經發布,具體可以參考Unicode官方站點:

www.unicode.org

Unicode編碼方案主要有三個實施標準:

UTF-8

USC-2

UTF-16

Oracle7.2開始支持UTF-8編碼,提供Unicode編碼支持。

按照各種標準的含義,Oracle推薦,如果你的數據庫需要存放不同語言的不同符號和字符,建議使用Unicode編碼方案。誠然,Unicode方案可以表示更多的字符,但是由於多位的存儲,需要額外的存儲空間和網絡傳輸,所以選擇最適合的數據庫字符集仍然需要慎重考慮。

2 數據庫的字符集

 字符集在創建數據庫時指定,在創建後通常不能更改,所以在創建數據庫時能否選擇一個正確的字符集就顯得尤爲重要。

在創建數據庫時,我們可以指定字符集(CHARACTER SET)和國家字符集(NATIONAL CHARACTER SET)

字符集用來存儲:

     CHAR, VARCHAR2, CLOB, LONG等類型數據

     用來標示諸如表名、列名以及PL/SQL變量等

     SQLPL/SQL程序單元等

國家字符集用以存儲:

     NCHAR, NVARCHAR2, NCLOB等類型數據

這些設置在數據庫創建時指定,我們可以看一下數據庫的創建腳本:

connect SYS/change_on_install as SYSDBA

set echo on

spool E:/oracle/ora92/assistants/dbca/logs/CreateDB.log

startup nomount pfile="E:/oracle/admin/eygle/scripts/init.ora";

CREATE DATABASE eygle

MAXINSTANCES 1

MAXLOGHISTORY 1

MAXLOGFILES 5

MAXLOGMEMBERS 3

MAXDATAFILES 100

DATAFILE 'E:/oracle/oradata/eygle/system01.dbf' SIZE 250M REUSE AUTOEXTEND ON NEXT 10240K MAXSIZE UNLIMITED

EXTENT MANAGEMENT LOCAL

DEFAULT TEMPORARY TABLESPACE TEMP TEMPFILE 'E:/oracle/oradata/eygle/temp01.dbf' SIZE 40M REUSE AUTOEXTEND 

ON NEXT 640K MAXSIZE UNLIMITED

UNDO TABLESPACE "UNDOTBS1" DATAFILE 'E:/oracle/oradata/eygle/undotbs01.dbf' SIZE 50M REUSE AUTOEXTEND 

ON NEXT 5120K MAXSIZE UNLIMITED

CHARACTER SET ZHS16GBK

NATIONAL CHARACTER SET AL16UTF16

LOGFILE GROUP 1 ('E:/oracle/oradata/eygle/redo01.log') SIZE 10M,

GROUP 2 ('E:/oracle/oradata/eygle/redo02.log') SIZE 10M,

GROUP 3 ('E:/oracle/oradata/eygle/redo03.log') SIZE 10M;

spool off

exit;

以上用粗體顯示的就是對我們至關重要的字符集設置。

在創建數據庫的過程中,在以下界面選擇你的字符集,對於簡體中文平臺,缺省的字符集是:ZHS16GBK

 

一旦你的字符集選定了,數據庫中能夠存儲的字符就受到了限制,所以你選擇的字符集的應該可以容納所有你將用到字符。

常見的中文字符集有:

 

ZHS16CGB231280  CGB2312-80  16-bit Simplified Chinese MB, ASCII

ZHS16GBK      GBK   16-bit Simplified Chinese MB, ASCII, UDC

 

               

其中GB2312碼是中華人民共和國國家漢字信息交換用編碼,全稱《信息交換用漢字編碼字符集--基本集》,由國家標準總局發佈,

198151日實施,通行於大陸。新加坡等地也使用此編碼。 

GBK編碼是199512月頒佈的指導性規範。

GBK與國家標準 GB 2312-80 信息處理交換碼所對應的、事實上的內碼標準兼容;同時,在字彙一級支持 ISO/IEC 10646-1 GB 13000-1 的全部中日韓 (CJK) 漢字(20902)。包含了更多的編碼。

但是我們說,ZHS16GBK 並非是ZHS16CGB231280的嚴格超集(雖然後者的漢字在前者中都存在,但是同樣的編碼在不同兩個字符集中可能表達不同的漢字),所以在做數據庫字符轉換時仍然需要特別注意。

 

Oracle的字符集命名遵循以下命名規則:

 

     <Language><bit size><encoding>

:  <語言>    <比特位數><編碼>

比如: ZHS    · 16     ·GBK

 

 

               

需要說明的是,有些字符集命名違背了這個規範,Oracle8/Oralce8i中的UTF-8是第一個打破這個命名規範的字符集。

我們可以看到一類字符集以 AL開頭,如:

     AL16UTF16

其中 AL代表 ALL,指適用於所有語言(All Languages),按照這個標準當年UTF-8本應被命名爲AL24UTF8

3 字符集的更改

 

數據庫創建以後,如果需要修改字符集,通常需要重建數據庫,通過導入導出的方式來轉換。

我們也可以通過以下方式更改

 

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下進行,Oracle9iOracle8i在編碼方面有較大改變,在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<sid>.log日誌文件,看CLOB字段存在於哪些表上:

 

ALTER DATABASE CHARACTER SET ZHS16GBK

 SYS.METASTYLESHEET (STYLESHEET) - CLOB populated

ORA-12716 signalled during: ALTER DATABASE CHARACTER SET ZHS16GBK...

對於不同情況,Oracle提供不同的解決方案,如果是用戶數據表,一般我們可以把包含CLOB字段的表導出,然後drop掉相關對象,

轉換後再導入數據庫;對於系統表,可以按照以下方式處理:

SQL> truncate table Metastylesheet;

Table truncated.

然後可以繼續進行轉換!

 

SQL> ALTER SESSION SET SQL_TRACE=TRUE;

 

Session altered.

 

SQL> ALTER DATABASE CHARACTER SET ZHS16GBK;

 

Database altered.

 

SQL> ALTER SESSION SET SQL_TRACE=FALSE;

 

Session altered.

   

   

9.2.0中,轉換完成以後,可以通過運行catmet.sql腳本來重建Metastylesheet:

 

SQL> @?/rdbms/admin/catmet.sql

轉換後的數據:

 

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               ZHS16GBK

…..

NLS_NCHAR_CHARACTERSET         AL16UTF16

NLS_RDBMS_VERSION              9.2.0.4.0

 

20 rows selected.

 

SQL> select * from eygle.test;

 

NAME

------------------------------

測試

test

 

2 rows selected.

  

提示:

通過設置sql_trace,我們可以跟蹤很多數據庫的後臺操作,這個工具是DBA常用的利器之一。

我們簡單看一下數據庫更改字符集時的後臺處理,我提取了主要的更新部分。

通過以下跟蹤過程,我們看到數據庫在更改字符集的時候,主要更新了12張數據字典表,修改了數據庫的原數據,這也證實了我們以前的說法: 

這個更改字符集的操作在本質上並不轉換任何數據庫字符,只是簡單的更新數據庫中所有跟字符集相關的信息。

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'

   

   

 

在這裏我們順便糾正一個由來以及的錯誤方法.

經常可以在網上看到這樣的更改字符集的方法:

 

1)用SYS用戶名登陸ORACLE

2)查看字符集內容

SQL>SELECT * FROM PROPS$;

3)修改字符集

SQL> update props$ set value$='新字符集' where name='NLS_CHARACTERSET'

4) COMMIT;

 

我們看到很多人在這個問題上遇到了慘痛的教訓,使用這種方式更改字符集,如果你的value$值輸入了不正確的字符集,在8i中那麼你的數據庫可能會無法啓動,這種情況是非常嚴重的,有時候你必須從備份中進行恢復;如果是在9i中,可以重新啓動數據庫後再修改回正確的字符集。但是我們仍然不建議使用這種方式進行任何數據庫修改,這是一種極其危險的操作。

實際上當我們更新了字符集,數據庫啓動時會根據數據庫的字符集自動的來修改控制文件的字符集,如果字符集可以識別,更新控制文件字符集等於數據庫字符集;如果字符集不可識別,那麼控制文件字符集更新爲US7ASCII.

通過更新props$表的方式修改字符集,Oracle7之後就不應該被使用.

以下是我的測試結果,但是嚴禁一切不備份的修改研究,即使是對測試庫的。

SQL> update props$ set value$='EYGLE' where name='NLS_CHARACTERSET';

 

1 row updated.

 

SQL> commit;

 

Commit complete.

 

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               EYGLE

NLS_CALENDAR                   GREGORIAN

NLS_DATE_FORMAT                DD-MON-RR

NLS_DATE_LANGUAGE              AMERICAN

….

NLS_NCHAR_CHARACTERSET         ZHS16GBK

NLS_RDBMS_VERSION              8.1.7.1.1

 

18 rows selected.

 

重新啓動數據庫,發現alert.log文件中記錄如下操作:

 

Mon Nov 03 16:11:35 2003

Updating character set in controlfile to US7ASCII

Completed: ALTER DATABASE OPEN

 

啓動數據庫後恢復字符集設置:

 

SQL> update props$ set value$='ZHS16GBK' where name='NLS_CHARACTERSET';

 

1 row updated.

 

SQL> commit;

 

Commit complete.

 

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               ZHS16GBK

NLS_CALENDAR                   GREGORIAN

NLS_DATE_FORMAT                DD-MON-RR

NLS_DATE_LANGUAGE              AMERICAN

………

NLS_COMP                       BINARY

NLS_NCHAR_CHARACTERSET         ZHS16GBK

NLS_RDBMS_VERSION              8.1.7.1.1

 

18 rows selected.

 

重新啓動數據庫後,發現控制文件的字符集被更新:

 

Mon Nov 03 16:21:41 2003

Updating character set in controlfile to ZHS16GBK

Completed: ALTER DATABASE OPEN

 

 

理解了字符集調整的內部操作以後,我們可以輕易的指出,以上的方法是不正確的,通過前面 ” ALTER DATABASE CHARACTER SET” 方式更改字符集時,Oracle至少需要更改12張數據字典表,而這種直接更新props$表的方式只完成了其中十二分之一的工作,潛在的完整性隱患是可想而知的。

所以,更改字符集儘量要使用正常的途徑

4 導入導出及轉換

 

導入導出是我們常用的一個數據遷移及轉化工具,因其導出文件具有平臺無關性,所以在跨平臺遷移中,最爲常用。

在導出操作時,非常重要的是客戶端的字符集設置,也就是客戶端的NLS_LANG設置。

NLS_LANG參數由以下部分組成:

 

 

NLS_LANG=<Language>_<Territory>.<Clients Characterset>

NLS_LANG各部分含義如下:

LANGUAGE指定:

-Oracle消息使用的語言

-日期中月份和日顯示

TERRITORY指定

-貨幣和數字格式

-地區和計算星期及日期的習慣

CHARACTERSET:

-控制客戶端應用程序使用的字符集

通常設置或者等於客戶端(Windows)代碼頁

或者對於unicode應用設置爲UTF8

Windows上查看當前系統的代碼頁可以使用chcp命令:

 

 

E:/>chcp

 

 活動的代碼頁: 936

 

代碼頁936也就是中文字符集 GBK,Microsoft的官方站點上,我們可以遭到關於936代碼頁的具體編碼規則,請參考以下鏈接:

http://www.microsoft.com/globaldev/reference/dbcs/936.htm

我們看一個簡單的測試,來了解一下這幾個參數的作用:

 

E:/>set NLS_LANG=SIMPLIFIED CHINESE_CHINA.ZHS16GBK

 

E:/>sqlplus "/ as sysdba"

 

SQL*Plus: Release 9.2.0.4.0 - Production on 星期六 11 1 22:51:59 2003

 

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

 

 

連接到:

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> select sysdate from dual;

 

SYSDATE

----------

01-11-03

 

已選擇 1 行。

 

SQL> exit

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中斷開

 

E:/>set NLS_LANG=AMERICAN_AMERICA.ZHS16GBK

 

E:/>sqlplus "/ as sysdba"

 

SQL*Plus: Release 9.2.0.4.0 - Production on Sat Nov 1 22:52:24 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> select sysdate from dual;

 

SYSDATE

---------

01-NOV-03

 

1 row selected.

 

SQL>

   

查看客戶端NLS_LANG設置可以使用以下方法:

 

Windows使用:

 

echo %NLS_LANG%

:

E:/>echo %NLS_LANG%

AMERICAN_AMERICA.ZHS16GBK

 

 

 

 

Unix使用:

 

env|grep NLS_LANG

:

/opt/oracle>env|grep NLS_LANG

NLS_LANG=AMERICAN_CHINA.ZHS16GBK

 

 Windows客戶端設置,可以在註冊表中更改NLS_LANG,具體鍵值位於:

HKEY_LOCAL_MACHINE/SOFTWARE/ORACLE/HOMExx/

xx指存在多個ORACLE_HOME時系統編號。

導入和導出是客戶端產品,同SQL*PLUSOralce Forms一樣,因此,使用EXP/IMP工具將按照NLS_LANG定義的方式轉換字符集。

導出使用的字符集將會記錄在導出文件中,當文件導入時,將會檢查導出時使用的字符集設置,如果這個字符集不同於導入客戶端的NLS_LANG設置,字符集將根據導入客戶端NLS_LANG設置進行轉換,如果必要,在數據插入數據庫之前會進行進一步轉換。

通常在導出時最好把客戶端字符集設置得和數據庫端相同,這樣可以避免在導出時發生不必要的數據轉換,導出文件將和數據庫具有相同的字符集。

即使將來會把導出文件導入到不同字符集的數據庫中,這樣做也可以把轉換延緩至導入時刻。

當進行數據導入時,主要存在以下兩種情況:

1.源數據庫和目標數據庫具有相同字符集設置

這時,只需要設置NLS_LANG等於數據庫字符集即可導入(前提是,導出使用的是和源數據庫相同字符集,即三者相同)

2.源數據庫和目標數據庫字符集不同

如果我們導出時候使用的NLS_LANG是和源數據庫相同的字符集,那麼導入時就可以設置客戶端NLS_LANG等於導出時使用的字符集,這樣轉換隻發生在數據庫端,而且只發生一次。

例如:

如果進行從WE8MSWIN1252UTF8的轉換

1)使用NLS_LANG=AMERICAN_AMERICA.WE8MSWIN1252導出數據庫。

這時創建的導出文件包含WE8MSWIN1252的數據

2)導入時使用NLS_LANG=AMERICAN_AMERICA.WE8MSWIN1252

這時轉換僅發生在insert數據到UTF8的數據庫中。

以上假設的轉換隻在目標數據庫字符集是源數據庫字符集的超集時才能轉換。如果不同,一般就需要進行一些特殊的處理。

我們簡單看一下導入的轉換過程(Oracle8i爲例)

1.確定導出數據庫字符集環境

通過讀取導出文件頭,可以獲得導出文件的字符集設置

2.確定導入session的字符集,即導入Session使用的NLS_LANG環境變量

3.IMP讀取導出文件

讀取導出文件字符集ID,和導入進程的NLS_LANG進行比較

4.如果導出文件字符集和導入Session字符集相同,那麼在這一步驟內就不需要轉換

如果不同,就需要把數據轉換爲導入Session使用的字符集。

然而這種轉換只能在單byte字符集之間進行。

我們看一個測試:

 

E:/nls2>set NLS_LANG=AMERICAN_AMERICA.US7ASCII

 

設置導入session NLS_LANGUS7ASCII

 

E:/nls2>e:/oracle/ora8i/bin/imp eygle/eygle file=Sus7ascii-Cus7ascii-exp817.dmp fromuser=eygle touser=eygle tables=test

 

這個導出文件是從US7ASCII數據庫導出,導出客戶端NLS_LANG也是US7ASCII

 

Import: Release 8.1.7.1.1 - Production on Fri Nov 7 00:59:22 2003

 

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

 

Connected to: Oracle8i Enterprise Edition Release 8.1.7.1.1 - Production

With the Partitioning option

JServer Release 8.1.7.1.1 - Production

 

這時導入,在DMP文件和NLS_LANG之間不需要進行字符集轉換。

 

Export file created by EXPORT:V08.01.07 via conventional path

import done in US7ASCII character set and ZHS16GBK NCHAR character set

import server uses ZHS16GBK character set (possible charset conversion)

export server uses UTF8 NCHAR character set (possible ncharset conversion)

. . importing table                         "TEST"          2 rows imported

Import terminated successfully without warnings.

   

5.對於多Byte字符集的導入(:UTF8)

需要設置導入Session字符集和導出字符集相同

否則就會遇到:IMP-16 "Required character set conversion (type %lu to %lu) not supported" 錯誤。

:

E:/nls2>set NLS_LANG=AMERICAN_AMERICA.ZHS16GBK

 

導入Session字符集設置爲ZHS16GBK

導入US7ASCII的導出文件

 

E:/nls2>e:/oracle/ora8i/bin/imp eygle/eygle file=Sus7ascii-Cus7ascii-exp817.dmp fromuser=eygle touser=eygle

 

Import: Release 8.1.7.1.1 - Production on Fri Nov 7 00:38:55 2003

 

(c) Copyright 2000 Oracle Corporation.  All rights reserved.

 

 

Connected to: Oracle8i Enterprise Edition Release 8.1.7.1.1 - Production

With the Partitioning option

JServer Release 8.1.7.1.1 - Production

 

IMP-00016: required character set conversion (type 1 to 852) not supported

IMP-00000: Import terminated unsuccessfully

 

在從導出文件US7ASCII到導入 NLS_LANG設置爲ZHS16GBK的過程中,不支持單Byte字符集向多Byte轉換,報出以上錯誤。

   

6.導入Session字符集應該是導出字符集的超級,否則,專有的字符將難以正確轉換。

7.當數據轉換爲導入Session字符集設置以後,如果導入Session字符集不同於導入數據庫字符集,這時還需要最後一步轉換,這要求導入數據庫字符集是導入session字符集的超級,否則某些專有字符將不能正常轉換。

我們繼續看上面的兩個過程,這裏有這樣兩個原則:

1.如果NLS_LANG的設置和數據庫相同,那麼數據(在傳輸過程中當然是2進制碼)不經過轉換就直接插入數據庫中。

2.如果NLS_LANG的設置和數據庫不同,那麼數據需要轉換後才能插入數據庫中。

我們再回頭來看上面的第一個例子:

:

Export file created by EXPORT:V08.01.07 via conventional path

import done in US7ASCII character set and ZHS16GBK NCHAR character set

import server uses ZHS16GBK character set (possible charset conversion)

export server uses UTF8 NCHAR character set (possible ncharset conversion)

. . importing table                         "TEST"          2 rows imported

Import terminated successfully without warnings.

 

這時候經過第一步轉換後的數據,US7ASCIIZHS16GBK丟失首位,原樣插入數據庫,我們看到這時數據庫中存放的就是錯誤的字符(在後面部分我們做了詳細的轉換):

 

E:/nls2>sqlplus eygle/eygle

 

SQL*Plus: Release 9.2.0.4.0 - Production on Fri Nov 7 00:35:39 2003

 

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

 

 

Connected to:

Oracle8i Enterprise Edition Release 8.1.7.1.1 - Production

With the Partitioning option

JServer Release 8.1.7.1.1 - Production

 

SQL> select * from test;

 

NAME

--------------------

2bJT

test

   

Oracle9i中,以上情況略有不同。

5.  導出文件字符集

我們知道在導出文件中,記錄着導出使用的字符集id,通過查看導出文件頭的第23個字節,我們可以找到16進製表示的字符集ID,Windows上,我們可以使用UltraEdit等工具打開dmp文件,查看其導出字符集::

Unix上我們可以通過以下命令來查看:

cat expdat.dmp | od -x | head

                     

Oracle提供標準函數,對字符集名稱及ID進行轉換:

 

 

SQL> select nls_charset_id('ZHS16GBK') from dual;

 

NLS_CHARSET_ID('ZHS16GBK')

--------------------------

                       852

 

1 row selected.

 

SQL> select nls_charset_name(852) from dual;

 

NLS_CHAR

--------

ZHS16GBK

 

1 row selected.

 

十進制轉換十六進制:

 

SQL> select to_char('852','xxxx') from dual;

 

TO_CH

-----

  354

 

1 row selected.

      

對應上面的圖中第23字節,我們知道該導出文件字符集爲ZHS16GBk.

查詢數據庫中有效的字符集可以使用以下腳本:

 

 

col nls_charset_id for 9999

col nls_charset_name for a30

col hex_id for a20

select

nls_charset_id(value) nls_charset_id, 

value nls_charset_name,

to_char(nls_charset_id(value),'xxxx') hex_id

from  v$nls_valid_values

where parameter = 'CHARACTERSET'

order by nls_charset_id(value)

/

     

輸出樣例如下:

 

 

NLS_CHARSET_ID NLS_CHARSET_NAME               HEX_ID

-------------- ------------------------------ -------------

             1 US7ASCII                           1

             2 WE8DEC                             2

             3 WE8HP                              3

             4 US8PC437                           4

             5 WE8EBCDIC37                        5

             6 WE8EBCDIC500                       6

             7 WE8EBCDIC1140                      7

             8 WE8EBCDIC285                       8

 ...................

           850 ZHS16CGB231280                   352

           851 ZHS16MACCGB231280                353

           852 ZHS16GBK                         354

           853 ZHS16DBCS                        355

           860 ZHT32EUC                         35c

           861 ZHT32SOPS                        35d

           862 ZHT16DBT                         35e

           863 ZHT32TRIS                        35f

           864 ZHT16DBCS                        360

           865 ZHT16BIG5                        361

           866 ZHT16CCDC                        362

           867 ZHT16MSWIN950                    363

           868 ZHT16HKSCS                       364

           870 AL24UTFFSS                       366

           871 UTF8                             367

           872 UTFE                             368

..................................

在很多時候,當我們進行導入操作的時候,已經離開了源數據庫,這時如果目標數據庫的字符集和導出文件不一致,很多時候就需要進行特殊處理,

以下介紹幾種方法,主要以US7ASCIIZHS16GBK爲例

1 源數據庫字符集爲US7ASCII,導出文件字符集爲US7ASCIIZHS16GBK,目標數據庫字符集爲ZHS16GBK

Oracle92中,我們發現對於這種情況,不論怎樣處理,這個導出文件都無法正確導入到Oracle9i數據庫中,這可能是因爲Oracle9i的編碼方案發生了較大改變。

以下是我們所做的簡單測試,其中導出文件命名規則爲:

 

S-Server ,後跟Server字符集

C-client , 後跟導出操作時客戶端字符集

 

導入時客戶端字符集設置在命令行完成,限於篇幅,我們省略了部分測試過程。

對於Oracle9iR2,我們的測試結果是US7ASCII字符集,不管怎樣轉換,都無法正確導入ZHS16GBK字符集的數據庫中。

在進行導入操作時,如果字符不能正常轉換,Oracle數據庫會自動用一個”?”代替,也就是編碼63

 

 

E:/nls2>set NLS_LANG=AMERICAN_AMERICA.US7ASCII

 

E:/nls2>imp eygle/eygle file=Sus7ascii-Cus7ascii.dmp fromuser=eygle touser=eygle tables=test

 

Import: Release 9.2.0.4.0 - Production on Mon Nov 3 17:14:39 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

 

Export file created by EXPORT:V09.02.00 via conventional path

import done in US7ASCII character set and AL16UTF16 NCHAR character set

import server uses ZHS16GBK character set (possible charset conversion)

. . importing table                         "TEST"          2 rows imported

Import terminated successfully without warnings.

 

E:/nls2>sqlplus eygle/eygle

 

SQL*Plus: Release 9.2.0.4.0 - Production on Mon Nov 3 17:14:50 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> select name,dump(name) from test;

 

NAME DUMP(NAME)

-----------------------------

???? Typ=1 Len=4: 63,63,63,63

test Typ=1 Len=4: 116,101,115,116

 

2 rows selected.

 

SQL> exit

Disconnected from 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

 

E:/nls2>set NLS_LANG=AMERICAN_AMERICA.ZHS16GBK

 

E:/nls2>imp eygle/eygle file=Sus7ascii-Cus7ascii.dmp fromuser=eygle touser=eygle tables=test ignore=y

 

Import: Release 9.2.0.4.0 - Production on Mon Nov 3 17:15:28 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

 

Export file created by EXPORT:V09.02.00 via conventional path

import done in ZHS16GBK character set and AL16UTF16 NCHAR character set

export client uses US7ASCII character set (possible charset conversion)

. . importing table                         "TEST"          2 rows imported

Import terminated successfully without warnings.

 

E:/nls2>sqlplus eygle/eygle

 

SQL*Plus: Release 9.2.0.4.0 - Production on Mon Nov 3 17:15:34 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> select name,dump(name) from test;

 

NAME  DUMP(NAME)

--------------------------------------------------------------------------------

????  Typ=1 Len=4: 63,63,63,63

test  Typ=1 Len=4: 116,101,115,116

????  Typ=1 Len=4: 63,63,63,63

test  Typ=1 Len=4: 116,101,115,116

 

 

4 rows selected.

 

SQL> drop table test;

 

Table dropped.

 

SQL> exit

Disconnected from 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

 

E:/nls2>set NLS_LANG=AMERICAN_AMERICA.ZHS16GBK

 

E:/nls2>imp eygle/eygle file=Sus7ascii-Czhs16gbk.dmp fromuser=eygle touser=eygle tables=test ignore=y

 

Import: Release 9.2.0.4.0 - Production on Mon Nov 3 17:17:21 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

 

Export file created by EXPORT:V09.02.00 via conventional path

import done in ZHS16GBK character set and AL16UTF16 NCHAR character set

. . importing table                         "TEST"          2 rows imported

Import terminated successfully without warnings.

 

E:/nls2>sqlplus eygle/eygle

 

SQL*Plus: Release 9.2.0.4.0 - Production on Mon Nov 3 17:17: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> select name,dump(name) from test;

 

NAME DUMP(NAME)

----------------------------------------------

???? Typ=1 Len=4: 63,63,63,63

test Typ=1 Len=4: 116,101,115,116

 

2 rows selected.

 

SQL> exit

Disconnected from 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

 

E:/nls2>set NLS_LANG=AMERICAN_AMERICA.US7ASCII

 

E:/nls2>imp eygle/eygle file=Sus7ascii-Czhs16gbk.dmp fromuser=eygle touser=eygle tables=test ignore=y

 

Import: Release 9.2.0.4.0 - Production on Mon Nov 3 17:18:00 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

 

Export file created by EXPORT:V09.02.00 via conventional path

import done in US7ASCII character set and AL16UTF16 NCHAR character set

import server uses ZHS16GBK character set (possible charset conversion)

export client uses ZHS16GBK character set (possible charset conversion)

. . importing table                         "TEST"          2 rows imported

Import terminated successfully without warnings.

 

E:/nls2>sqlplus eygle/eygle

 

SQL*Plus: Release 9.2.0.4.0 - Production on Mon Nov 3 17:18:08 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> select name,dump(name) from test;

 

NAME DUMP(NAME)

----------------------------------------

???? Typ=1 Len=4: 63,63,63,63

test Typ=1 Len=4: 116,101,115,116

???? Typ=1 Len=4: 63,63,63,63

test Typ=1 Len=4: 116,101,115,116

 

4 rows selected.

 

SQL>

對於這種情況,我們可以通過使用Oracle8i的導出工具,設置導出字符集爲US7ASCII,導出後修改第二、三字符,修改 0001 0354,這樣就可以將US7ASCII字符集的數據正確導入到ZHS16GBK的數據庫中。

修改導出文件:

導入修改後的導出文件:

 

 

E:/nls2>set NLS_LANG=AMERICAN_AMERICA.ZHS16GBK

 

E:/nls2>imp eygle/eygle file=Sus7ascii-Cus7ascii-exp817.dmp fromuser=eygle touser=eygle tables=test

 

Import: Release 9.2.0.4.0 - Production on Mon Nov 3 17:37:17 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

 

Export file created by EXPORT:V08.01.07 via conventional path

import done in ZHS16GBK character set and AL16UTF16 NCHAR character set

export server uses UTF8 NCHAR character set (possible ncharset conversion)

. . importing table                         "TEST"          2 rows imported

Import terminated successfully without warnings.

 

E:/nls2>sqlplus eygle/eygle

 

SQL*Plus: Release 9.2.0.4.0 - Production on Mon Nov 3 17:37:23 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> select name,dump(name) from test;

 

NAME     DUMP(NAME)

--------------------------------------------------------------------------------

測試       Typ=1 Len=4: 178,226,202,212

Test        Typ=1 Len=4: 116,101,115,116

 

2 rows selected.

 

SQL>

2 使用create database的方法

如果導出文件使用的字符集是US7ASCII,目標數據庫的字符集是ZHS16GBK,我們可以使用create database的方法來修改,具體如下:

 

 

SQL> col parameter for a30

SQL> col value for a30

SQL> select * from v$nls_parameters;

 

PARAMETER                      VALUE

------------------------------ ------------------------------

NLS_LANGUAGE                   AMERICAN

NLS_TERRITORY                  AMERICA

NLS_CURRENCY                   $

NLS_ISO_CURRENCY               AMERICA

NLS_NUMERIC_CHARACTERS         .,

NLS_CALENDAR                   GREGORIAN

NLS_DATE_FORMAT                DD-MON-RR

NLS_DATE_LANGUAGE              AMERICAN

NLS_CHARACTERSET               ZHS16GBK

NLS_SORT                       BINARY

……………….

 

19 rows selected.

 

SQL> create database character set us7ascii;

create database character set us7ascii

*

ERROR at line 1:

ORA-01031: insufficient privileges

 

 

SQL> select * from v$nls_parameters;

 

PARAMETER                      VALUE

------------------------------ ------------------------------

NLS_LANGUAGE                   AMERICAN

NLS_TERRITORY                  AMERICA

NLS_CURRENCY                   $

NLS_ISO_CURRENCY               AMERICA

NLS_NUMERIC_CHARACTERS         .,

NLS_CALENDAR                   GREGORIAN

NLS_DATE_FORMAT                DD-MON-RR

NLS_DATE_LANGUAGE              AMERICAN

NLS_CHARACTERSET               US7ASCII

NLS_SORT                       BINARY

…………..

 

19 rows selected.

 

SQL> exit

Disconnected from 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

 

E:/nls2>set nls_lang=AMERICAN_AMERICA.US7ASCII

 

E:/nls2>imp eygle/eygle file=Sus7ascii-Cus7ascii.dmp fromuser=eygle touser=eygle

 

Import: Release 9.2.0.4.0 - Production on Sun Nov 2 14:53:26 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

 

Export file created by EXPORT:V09.02.00 via conventional path

import done in US7ASCII character set and AL16UTF16 NCHAR character set

import server uses ZHS16GBK character set (possible charset conversion)

. . importing table                         "TEST"          2 rows imported

Import terminated successfully without warnings.

 

E:/nls2>sqlplus eygle/eygle

 

SQL*Plus: Release 9.2.0.4.0 - Production on Sun Nov 2 14:53:35 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> select * from test;

 

NAME

----------

測試

test

 

2 rows selected.

我們看到,當發出create database character set us7ascii命令時,數據庫v$nls_parameters中的字符集設置隨之更改,該參數影響導入進程,

更改後可以正確導入數據,重起數據庫後,該設置恢復。

提示:v$nls_paraemters來源於x$nls_parameters,該動態性能視圖影響導入操作;而nls_database_parameters來源於props$數據表,影響數據存儲。

3 Oracle提供的字符掃描工具csscan

我們說以上的方法只是應該在不得已的情況下使用,其本質是欺騙數據庫,強制導入數據,可能損失元數據。

如果要確保數據的完整性,應該使用csscan掃描數據庫,找出所有不兼容的字符,然後通過編寫相應的腳本及代碼,在轉換之後進行更新,確保數據的正確性。

我們簡單看一下csscan的使用。

要使用csscan之前,需要以sys用戶身份創建相應數據字典對象:

 

 

E:/nls2>sqlplus "/ as sysdba"

 

SQL*Plus: Release 9.2.0.4.0 - Production on Sun Nov 2 19:42:07 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> select instance_name from v$instance;

 

INSTANCE_NAME

----------------

penny

 

1 row selected.

 

SQL> @?/rdbms/admin/csminst.sql

 

User created.

 

 

Grant succeeded.

 

………..

 

               

這個腳本創建相應用戶(csmig)及數據字典對象,掃描信息會記錄在相應的數據字典表裏。

我們可以在命令行調用這個工具對數據庫進行掃描:

 

E:/nls2>csscan FULL=Y FROMCHAR=ZHS16GBK TOCHAR=US7ASCII LOG=US7check.log CAPTURE=Y ARRAY=1000000 PROCESS=2

 

 

Character Set Scanner v1.1 : Release 9.2.0.1.0 - Production on Sun Nov 2 20:24:45 2003

 

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

 

 

Username: eygle/eygle

 

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

 

Enumerating tables to scan...

 

. process 1 scanning SYS.SOURCE$[AAAABHAABAAAAIRAAA]

. process 2 scanning SYS.ATTRIBUTE$[AAAAEoAABAAAAhZAAA]

. process 2 scanning SYS.PARAMETER$[AAAAEoAABAAAAhZAAA]

. process 2 scanning SYS.METHOD$[AAAAEoAABAAAAhZAAA]

……..

. process 2 scanning SYSTEM.DEF$_AQERROR[AAAA8fAABAAACWJAAA]

. process 1 scanning WMSYS.WM$ENV_VARS[AAABeWAABAAAFMZAAA]

………………….

. process 2 scanning SYS.UGROUP$[AAAAA5AABAAAAGpAAA]

. process 2 scanning SYS.CON$[AAAAAcAABAAAACpAAA]

. process 1 scanning SYS.FILE$[AAAAARAABAAAABxAAA]

 

Creating Database Scan Summary Report...

 

Creating Individual Exception Report...

 

Scanner terminated successfully.

     

然後我們可以檢查輸出的日誌來查看數據庫掃描情況:

 

Database Scan Individual Exception Report

 

 

[Database Scan Parameters]

 

Parameter                      Value                                          

------------------------------ ------------------------------------------------

Scan type                      Full database                                  

Scan CHAR data?                YES                                            

Current database character set ZHS16GBK                                        

New database character set     US7ASCII                                       

Scan NCHAR data?               NO                                             

Array fetch buffer size        1000000                                        

Number of processes            2                                              

Capture convertible data?      YES                                            

------------------------------ ------------------------------------------------

 

[Data Dictionary individual exceptions]

 

 

[Application data individual exceptions]

 

User  : EYGLE

Table : TEST

Column: NAME

Type  : VARCHAR2(10)

Number of Exceptions         : 1        

Max Post Conversion Data Size: 4        

 

ROWID              Exception Type      Size Cell Data(first 30 bytes)    

------------------ ------------------ ----- ------------------------------

AAABpIAADAAAAAMAAA lossy conversion         測試                         

------------------ ------------------ ----- ------------------------------

 

不能轉換的數據將會被記錄下來,我們可以根據這些信息在轉換之後,對數據進行相應的更新,確保轉換無誤。

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 SETZHS16GBK

 

c:/>set NLS_LANG=AMERICAN_AMERICA.US7ASCII

 

c:/>sqlplus eygle/eygle

 

SQL*Plus: Release 9.2.0.4.0 - Production on Tue Nov 4 01:19:57 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> 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.ZHS16GBK

c:/>sqlplus eygle/eygle

SQL*Plus: Release 9.2.0.4.0 - Production on Tue Nov 4 01:40:18 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> insert into test values('測試');

1 row created.

SQL> col dump(name) for a30

SQL> select name,dump(name) from test;

NAME DUMP(NAME)

---------- ------------------------------

測試 Typ=1 Len=4: 178,226,202,212

1 row selected.

我們把這個結果轉換爲2進製表示

          10110010  11100010  11001010  11010100

這個結果正是我們前面亂碼首位補全1後的結果。

這個測試說明在US7ASCII轉換中文的時候除去了首位的 1,這樣就丟失了元數據,導致亂碼出現,NLS_LANG的轉換作用由此可加一斑!

 

 

                                            

3 NLS_LANG和數據庫字符集相同時

在這種情況下,數據庫端對客戶端傳遞過來的編碼不進行任何轉換(這樣可以提高性能),直接存儲進入數據庫,那麼這時候就存在和上面同樣的問題,如果客戶端傳遞過來的字符集在數據庫中有正確的對應就可以正確存儲,如果沒有,就會被替換字符置換成?,亂碼就這樣產生了。

如上圖所示,當NLS_LANG和數據庫字符集設置相同都爲UTF8時,客戶端的歐元符號的編碼A4就不會經過任何轉換就插入到數據庫中,而在UTF8的數據庫中,A4代表的是一個非法字符。

 

我們來看一個簡單的測試

測試環境:

客戶端字符集應用爲中文GB18030

客戶端NLS_LANGUS7ASCII

數據庫字符集爲US7ASCII

我們知道這個時候,存入的數據,數據庫不進行任何轉換,在以下的測試中,我們看到中文在US7ASCII字符集下得以正確顯示。

 

 

c:/>set nls_lang=AMERICAN_AMERICA.US7ASCII

 

c:/>sqlplus eygle/eygle

 

SQL*Plus: Release 9.2.0.4.0 - Production on Tue Nov 4 01:02:04 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> insert into test values('測試');

 

1 row created.

 

SQL> commit;

 

Commit complete.

 

SQL> select * from test;

 

NAME

----------

測試

 

1 row selected.

 

SQL> col dump(name) for a30

SQL> select name,dump(name) from test;

 

NAME       DUMP(NAME)

---------- ------------------------------

測試       Typ=1 Len=4: 178,226,202,212

 

1 row selected.

 

SQL> select * from nls_database_parameters;

 

PARAMETER                      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_SORT                       BINARY

NLS_TIME_FORMAT                HH.MI.SSXFF AM

 

PARAMETER                      VALUE

------------------------------ ----------------------------------------

NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM

NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZR

NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZR

NLS_DUAL_CURRENCY              $

NLS_COMP                       BINARY

NLS_LENGTH_SEMANTICS           BYTE

NLS_NCHAR_CONV_EXCP            FALSE

NLS_NCHAR_CHARACTERSET         AL16UTF16

NLS_RDBMS_VERSION              9.2.0.4.0

 

20 rows selected.

 

SQL>

      

 

結語:

對於DBA來說,有一個很重要的原則就是:不要把你的數據庫置於危險的境地!

這就要求我們,在進行任何可能對數據庫結構發生改變的操作之前,先做有效的備份,很多DBA沒有備份的操作中得到了慘痛的教訓。

7.  字符集更改的內部操作

前面我們提到,通過修改props$的方式更改字符集在Oracle7之後是一種極其危險的方式,應該儘量避免。

 

我們又知道,通過ALTER DATABASE CHARACTER SET更改字符集雖然安全可靠,但是有嚴格的子集和超集的約束,實際上我們很少能夠用到這種方法。

 

實際上Oracle還存在另外一種更改字符集的方式.

如果你注意過的話,在Oraclealert<sid>.log文件中,你可能看到過這樣的日誌信息:

 

alter database character set INTERNAL_CONVERT ZHS16GBK

Updating 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 altered

Completed: alter database character set INTERNAL_CONVERT ZHS1

 

在這裏面,我們看到這樣一條重要的,Oracle非公開的命令:

alter database character set INTERNAL_CONVERT/ INTERNAL_USE ZHS16GBK

                     

 

這個命令是當你選擇了使用典型方式創建了種子數據庫以後,Oracle會根據你選擇的字符集設置,把當前種子數據庫的字符集更改爲期望字符集,這就是這條命令的作用.

在使用這個命令時,Oracle會跳過所有子集及超集的檢查,在任意字符集之間進行強制轉換,所以,使用這個命令時你必須十分小心,你必須清楚這一操作會帶來的風險.

我們之前講過的內容仍然有效,你可以使用csscan掃描整個數據庫,如果在轉換的字符集之間確認沒有嚴重的數據損壞,或者你可以使用有效的方式更改,你就可以使用這種方式進行轉換.

我們來看一下具體的操作過程及Oracle的內部操作:

SQL> shutdown immediate

Database closed.

Database dismounted.

ORACLE instance shut down.

SQL> startup mount

ORACLE instance started.

 

Total System Global Area  135337420 bytes

Fixed Size                   452044 bytes

Variable Size             109051904 bytes

Database Buffers           25165824 bytes

Redo Buffers                 667648 bytes

Database 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 ZHS16CGB231280

 

Database altered.

 

SQL>

                     

 

這是alert.log文件中的記錄信息:

 

Tue Oct 19 16:26:30 2004

Database Characterset is ZHS16GBK

replication_dependency_tracking turned off (no async multimaster replication found)

Completed: ALTER DATABASE OPEN

Tue Oct 19 16:27:07 2004

alter database character set INTERNAL_USE ZHS16CGB231280

Updating character set in controlfile to ZHS16CGB231280

Tue Oct 19 16:27:15 2004

Thread 1 advanced to log sequence 118

  Current log# 2 seq# 118 mem# 0: /opt/oracle/oradata/primary/redo02.log

Tue Oct 19 16:27:15 2004

ARC0: Evaluating archive   log 3 thread 1 sequence 117

ARC0: Beginning to archive log 3 thread 1 sequence 117

Creating archive destination LOG_ARCHIVE_DEST_1: '/opt/oracle/oradata/primary/archive/1_117.dbf'

ARC0: Completed archiving  log 3 thread 1 sequence 117

Tue Oct 19 16:27:20 2004

Completed: alter database character set INTERNAL_USE ZHS16CGB231280

Shutting down instance: further logons disabled

Shutting down instance (immediate)

License high water mark = 1

Tue Oct 19 16:29:06 2004

ALTER DATABASE CLOSE NORMAL

...

 

                     

格式化10046跟蹤文件,得到以下信息(摘要):

alter session set events '10046 trace name context forever,level 12'

 

 

alter database character set INTERNAL_USE ZHS16CGB231280

 

 

call     count       cpu    elapsed       disk      query    current        rows

------- ------  -------- ---------- ---------- ---------- ----------  ----------

Parse        1      0.00       0.00          0          0          0           0

Execute      1      4.88       6.04        910      16825      18099           0

Fetch        0      0.00       0.00          0          0          0           0

------- ------  -------- ---------- ---------- ---------- ----------  ----------

total        2      4.88       6.04        910      16825      18099           0

 

Misses in library cache during parse: 1

Optimizer goal: CHOOSE

Parsing user id: SYS

 

Elapsed 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.zip

http://www.eygle.com/special/primary_ora_13730.tkf.log

我們看到這個過程和之前ALTER DATABASE CHARACTER SET操作的內部過程是完全相同的,也就是說INTERNAL_USE提供的幫助就是使Oracle數據庫繞過了子集與超集的校驗.這一方法在某些方面是有用處的,比如測試;應用於產品環境大家應該格外小心,除了你以外,沒有人會爲此帶來的後果負責:

結語(我們不妨再說一次):

對於DBA來說,有一個很重要的原則就是:不要把你的數據庫置於危險的境地!

這就要求我們,在進行任何可能對數據庫結構發生改變的操作之前,先做有效的備份,很多DBA沒有備份的操作中得到了慘痛的教訓。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章