Oracle全球支持(即Globalization Support)允許我們使用本地語言和格式來存儲和檢索數據。通過全球支持,Oracle可以支持多種語言及字符集,得以展示數據庫的強大魅力。
由於不同語言及字符集的共同存儲存在設置上具有一定的複雜性,字符集一度成爲普遍困擾大家的一個主要問題。
本文試圖就一些常見問題進行探討,希望可以把一些實際經驗共享給大家!
1. 字符集的基本知識
如果從頭說起,字符集最早的編碼方案來自於與ASCII.這也是我們最常見的編碼方式。該方案起源於1960年代初期,最初是美國國會圖書館制定用來作爲美國圖書館界書目交換的共同標準,最後完善成爲美國的國家標準ASCII(American 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官方站點:
Unicode編碼方案主要有三個實施標準:
UTF-8
USC-2
UTF-16
Oracle從7.2開始支持UTF-8編碼,提供Unicode編碼支持。
按照各種標準的含義,Oracle推薦,如果你的數據庫需要存放不同語言的不同符號和字符,建議使用Unicode編碼方案。誠然,Unicode方案可以表示更多的字符,但是由於多位的存儲,需要額外的存儲空間和網絡傳輸,所以選擇最適合的數據庫字符集仍然需要慎重考慮。
2. 數據庫的字符集
字符集在創建數據庫時指定,在創建後通常不能更改,所以在創建數據庫時能否選擇一個正確的字符集就顯得尤爲重要。
在創建數據庫時,我們可以指定字符集(CHARACTER SET)和國家字符集(NATIONAL CHARACTER SET)。
字符集用來存儲:
CHAR, VARCHAR2, CLOB, LONG等類型數據
用來標示諸如表名、列名以及PL/SQL變量等
SQL和PL/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碼是中華人民共和國國家漢字信息交換用編碼,全稱《信息交換用漢字編碼字符集--基本集》,由國家標準總局發佈,
1981年5月1日實施,通行於大陸。新加坡等地也使用此編碼。
GBK編碼是1995年12月頒佈的指導性規範。
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下進行,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<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*PLUS和Oralce Forms一樣,因此,使用EXP/IMP工具將按照NLS_LANG定義的方式轉換字符集。
導出使用的字符集將會記錄在導出文件中,當文件導入時,將會檢查導出時使用的字符集設置,如果這個字符集不同於導入客戶端的NLS_LANG設置,字符集將根據導入客戶端NLS_LANG設置進行轉換,如果必要,在數據插入數據庫之前會進行進一步轉換。
通常在導出時最好把客戶端字符集設置得和數據庫端相同,這樣可以避免在導出時發生不必要的數據轉換,導出文件將和數據庫具有相同的字符集。
即使將來會把導出文件導入到不同字符集的數據庫中,這樣做也可以把轉換延緩至導入時刻。
當進行數據導入時,主要存在以下兩種情況:
1.源數據庫和目標數據庫具有相同字符集設置
這時,只需要設置NLS_LANG等於數據庫字符集即可導入(前提是,導出使用的是和源數據庫相同字符集,即三者相同)
2.源數據庫和目標數據庫字符集不同
如果我們導出時候使用的NLS_LANG是和源數據庫相同的字符集,那麼導入時就可以設置客戶端NLS_LANG等於導出時使用的字符集,這樣轉換隻發生在數據庫端,而且只發生一次。
例如:
如果進行從WE8MSWIN1252到UTF8的轉換
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_LANG爲US7ASCII 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. 這時候經過第一步轉換後的數據,US7ASCII到ZHS16GBK丟失首位,原樣插入數據庫,我們看到這時數據庫中存放的就是錯誤的字符(在後面部分我們做了詳細的轉換): 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,通過查看導出文件頭的第2、3個字節,我們可以找到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.
|
對應上面的圖中第2、3字節,我們知道該導出文件字符集爲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 .................................. |
在很多時候,當我們進行導入操作的時候,已經離開了源數據庫,這時如果目標數據庫的字符集和導出文件不一致,很多時候就需要進行特殊處理,
以下介紹幾種方法,主要以US7ASCII和ZHS16GBK爲例
1. 源數據庫字符集爲US7ASCII,導出文件字符集爲US7ASCII或ZHS16GBK,目標數據庫字符集爲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 SET爲ZHS16GBK
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_LANG爲US7ASCII
數據庫字符集爲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還存在另外一種更改字符集的方式.
如果你注意過的話,在Oracle的alert<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沒有備份的操作中得到了慘痛的教訓。