oracle的字符集設定與亂碼
字符集問題一直叫人頭疼,究其原因還是不能完全明白其運作原理。
在整個執行環節中,字符集在3個環節中發揮作用:
1.軟體在作業系統上運作時的對使用者的顯示,此時採用作業系統定義的字符集進行顯示。我們在系統I/O程式設計的時候經常要指定字符集,C#中的Text.Encoding=Encoding.Default實際上就是告訴編譯器,文字使用系統定義的預設字符集進行編碼。sqlplus也是執行在作業系統上的軟體,當然要使用系統所指定的字符集對外顯示內容。
2.資料向oracle服務端傳送前的通告。也就是sqlplus告訴伺服器現在使用的字符集是什麼。
3.資料流到達伺服器後,按照伺服器所使用的字符集自動翻譯客戶端的資料,然後儲存進系統。
在客戶端sqlplus和服務端傳送資料,資料會按照服務端字符集進行翻譯,這個過程是自動完成的不需要人工干預。任何時候,oracle服務端總是按照自己的字符集設定來存取資料,客戶端要想正確顯示從服務端讀取到的資料,也需要按照本地的字符集設定進行翻譯,這個過程也是自動的。
伺服器端需要採用合適的字符集進行資料儲存,這個很容易理解,ASCII字符集沒辦法用來儲存中文漢字,因為它根本沒有描述漢字所需要的編碼空間。
問題常常存在於客戶端與服務端通訊的過程中,sqlplus作為執行在作業系統上的軟體,無論是顯示還是通訊,必然使用作業系統所使用的字符集設定。無論sqlplus設定的字符集,作用只有一個,那就是通告伺服器端,為相互之間的字符集翻譯做準備。
客戶端的字符集設定是在NLS_LANG環境變數中設定的,客戶讀端的字符集可以和oracle客戶端設定得不一樣(本來人家就是自動翻譯的),但是客戶端字符集一定要和作業系統設定的字符集相匹配!
考慮一下,sqlplus使用的是作業系統的字符集定義在顯示和傳送資料(架設是TYPE_A),卻告訴oracle伺服器自己使用的字符集是TYPE_B,oracle伺服器會怎麼辦?它會將客戶端傳送過來的TYPE_A資料當作TYPE_B字符集格式用自身的TYPE_C字符集進行翻譯,然後再儲存進系統,這就形成了亂碼。反向的過程類似,Oracle伺服器發出的資料格式沒有疑問是TYPE_C,但是客戶端軟體認為自己使用的編碼是TYPE_B並進行了翻譯,交給作業系統用TYPE_A字符集總的字元/編碼對映關係進行翻譯顯示,最終導致了無法正確顯示。
一個現實的例子:RHEL5.8作業系統安裝了中文支援包以後,所有的語言環境都被設定成了zh_CN.UTF-8(通過LANG環境變數可知,也可通過locale命令查到),資料庫伺服器所使用的字符集為ZHS16GBK,很明顯,這兩者不一致,sqlplus在沒有設定NLS_LANG環境變數時,與資料庫保持一致,此時,從服務端取得的資料不需要翻譯,被sqlplus讀取並用zh_CN.UTF-8的字元/編碼對映關係進行翻譯顯示,所有的漢字變成了“?”。
根據上面的分析,要解決這一問題,要把sqlplus的字符集設定成和作業系統一致即可,作業系統設定的是zh_CN.UTF-8,但在.bash_profile裡面還不能直接將NLS_LANG設定為zh_CN.UTF-8,因為這個zh_CN.UTF8是字符集的localeID而不是字符集的名稱,真正的名稱叫SIMPLIFIEDCHINESE_CHINA.AL32UTF8,如果設定成zh_CN.UTF8,oracle會報ORA-12705: Cannotaccess NLS data files or invalid environmentspecified錯誤。在.bash_profile裡面加入NLS_LANG="SIMPLIFIEDCHINESE_CHINA.AL32UTF8"; export NLS_LANG問題就解決了。
下表是locale ID與字符集名稱的對應關係:
Language |
Locale ID |
NLS_LANG |
English (American) |
en_US.UTF-8 |
AMERICAN_AMERICA.AL32UTF8 |
English (American) |
en_US.ISO-8859-1 |
AMERICAN_AMERICA.WE8ISO8859P1 |
English (American) |
en_US.ISO-8859-15 |
AMERICAN_AMERICA.WE8ISO8859P15 |
English (Australian) |
en_AU.UTF-8 |
ENGLISH_AUSTRALIA.AL32UTF8 |
English (Australian) |
en_AU.ISO-8859-1 |
ENGLISH_AUSTRALIA.WE8ISO8859P1 |
English (Australian) |
en_AU.ISO-8859-15 |
ENGLISH_AUSTRALIA.WE8ISO8859P15 |
English (British) |
en_GB.UTF-8 |
ENGLISH_UNITED KINGDOM.AL32UTF8 |
English (British) |
en_GB.ISO-8859-1 |
ENGLISH_UNITED KINGDOM.WE8ISO8859P1 |
English (British) |
en_GB.ISO-8859-15 |
ENGLISH_UNITEDKINGDOM.WE8ISO8859P15 |
English (Ireland) |
en_IE.UTF-8 |
ENGLISH_IRELAND.AL32UTF8 |
English (Ireland) |
en_IE.ISO-8859-1 |
ENGLISH_IRELAND.WE8ISO8859P1 |
English (Ireland) |
en_IE.ISO-8859-15 |
ENGLISH_IRELAND.WE8ISO8859P15 |
German |
de_DE.UTF-8 |
GERMAN_GERMANY.AL32UTF8 |
German |
de_DE.ISO-8859-1 |
GERMAN_GERMANY.WE8ISO8859P1 |
German |
de_DE.ISO-8859-15 |
GERMAN_GERMANY.WE8ISO8859P15 |
French |
fr_FR.UTF-8 |
FRENCH_FRANCE.AL32UTF8 |
French |
fr_FR.ISO-8859-1 |
FRENCH_FRANCE.WE8ISO8859P1 |
French |
fr_FR.ISO-8859-15 |
FRENCH_FRANCE.WE8ISO8859P15 |
Italian |
it_IT.UTF-8 |
ITALIAN_ITALY.AL32UTF8 |
Italian |
it_IT.ISO-8859-1 |
ITALIAN_ITALY.WE8ISO8859P1 |
Italian |
it_IT.ISO-8859-15 |
ITALIAN_ITALY.WE8ISO8859P15 |
Spanish |
es_ES.UTF-8 |
SPANISH_SPAIN.AL32UTF8 |
Spanish |
es_ES.ISO-8859-1 |
SPANISH_SPAIN.WE8ISO8859P1 |
Spanish |
es_ES.ISO-8859-15 |
SPANISH_SPAIN.WE8ISO8859P15 |
Spanish (Mexico) |
es_MX.UTF-8 |
MEXICAN SPANISH_MEXICO.AL32UTF8 |
Spanish (Mexico) |
es_MX.ISO-8859-1 |
MEXICAN SPANISH_MEXICO.WE8ISO8859P1 |
Spanish (Mexico) |
es_MX.ISO-8859-15 |
MEXICANSPANISH_MEXICO.WE8ISO8859P15 |
Portuguese (Brazilian) |
pt_BR.UTF-8 |
BRAZILIANPORTUGUESE_BRAZIL.AL32UTF8 |
Portuguese (Brazilian) |
pt_BR.ISO-8859-1 |
BRAZILIANPORTUGUESE_BRAZIL.WE8ISO8859P1 |
Portuguese (Brazilian) |
pt_BR.ISO-8859-15 |
BRAZILIANPORTUGUESE_BRAZIL.WE8ISO8859P15 |
Japanese |
ja_JP.EUC-JP |
JAPANESE_JAPAN.JA16EUC |
Japanese |
ja_JP.UTF-8 |
JAPANESE_JAPAN.AL32UTF8 |
Korean |
ko_KR.EUC-KR |
KOREAN_KOREA.KO16KSC5601 |
Korean |
ko_KR.UTF-8 |
KOREAN_KOREA.AL32UTF8 |
Chinese (simplified) |
zh_CN.GB18030 |
SIMPLIFIEDCHINESE_CHINA.ZHS32GB18030 |
Chinese (simplified) |
zh_CN.UTF-8 |
SIMPLIFIED CHINESE_CHINA.AL32UTF8 |
Chinese (traditional) |
zh_TW.BIG5 |
TRADITIONALCHINESE_TAIWAN.ZHT16BIG5 |
Chinese (traditional) |
zh_TW.UTF-8 |
TRADITIONAL CHINESE_TAIWAN |
步驟可以歸納為:1.找到作業系統使用的字符集,並按上表找到對應的字符集名稱。2.修改客戶端軟體的字符集NLS_LANG環境變數設定。
不同平臺的一些細節:
Windows( 如簡體系統為:ZHS16GBK,繁體系統為:MSWIN950 )
1、設定session變數
# 常用中文字符集
set NLS_LANG=SIMPLIFIED CHINESE_CHINA.ZHS16GBK
# 常用unicode字符集
set NLS_LANG=american_america.AL32UTF8
2、可以通過修改登錄檔鍵值永久設定
HKEY_LOCAL_MACHINE/SOFTWARE/ORACLE/HOMExx/NLS_LANG
3、設定環境變數
NLS_LANG=SIMPLIFIED CHINESE_CHINA.ZHS16GBK
NLS_LANG=american_america.AL32UTF8
Unix/Linus:
1、設定session變數
# 常用unicode字符集
export NLS_LANG=american_america.AL32UTF8
# 常用中文字符集
export NLS_LANG="Simplified Chinese_china".ZHS16GBK
3、設定環境變數
可以編輯 bash_profile檔案進行永久設定
# vi .bash_profile
NLS_LANG="Simplified Chinese_china".ZHS16GBK
export NLS_LANG
# 使 bash_profile 設定生效
source .bash_profile
其他:
$ echo
$NLS_LANG
2、檢視系統編碼:
$ locale
3、檢視資料庫字符集,執行如下查詢:
select userenv('language') from dual;
2013年5月15日
轉載請註明出處------------------------------------------------------------------------------------------------------------------------
萬國碼(Unicode):包含了幾乎人類所有可用的字元,每年還在不斷的增加,可以看作是一種通用的字符集。它將全世界所有的字元統一化,統一編碼,不會再出現字元不相容和字元轉換的問題。它有以下三種編碼方式:- UTF-32編碼:固定使用4個位元組來表示一個字元,存在空間利用效率的問題。
- UTF-16編碼:對相對常用的60000餘個字元使用兩個位元組進行編碼,其餘的使用4位元組。
- UTF- 8編碼:相容ASCII編碼;拉丁文、希臘文等使用兩個位元組;包括漢字在內的其它常用字元使用三個位元組;剩下的極少使用的字元使用四個位元組。
Oracle字符集基本原理
在搞懂Oracle字符集基本原理之前,一定要先分清以下三個概念: 1. Oracle資料庫伺服器字符集:即Oracle以哪種字元編碼儲存字元,可以通過以下語句查出資料庫字符集的設定。 [sql] view plaincopyprint?- SQL> select * from v$nls_parameters where parameter='NLS_CHARACTERSET';
- PARAMETER VALUE
- ------------------------------ -----------------
- NLS_CHARACTERSET AL32UTF8
2. 客戶端作業系統字符集:即客戶端作業系統以哪種字元編碼儲存字元。
如果是Windows,可以使用chcp命令獲得內碼表(code page):
[sql] view plaincopyprint?- C:\Users\xianzhu>chcp
- Active code page: 936
根據該內碼表,到微軟的官方文件《National Language Support (NLS) API Reference》找到其對應的字符集。
如果是Linux,字符集在/etc/sysconfig/i18n設定:
[plain] view plaincopyprint?- LANG="zh_CN.GB2312" (指定當前作業系統的字符集)
- SUPPORTED="zh_CN.GB2312"(指定當前作業系統支援的字符集)
- SYSFONT="lat0-sun16"(指定當前作業系統的字型)
3. 客戶端NLS_LANG引數:該引數用於向Oracle指示客戶端作業系統的字符集。
有了以上3個基本概念之後,我來闡述一下Oracle字符集轉換的基本原則:
- 設定客戶端的NLS_LANG為客戶端作業系統的字符集
- 如果資料庫字符集等於NLS_LANG,資料庫和客戶端傳輸字元時不作任何轉換
- 如果它們倆不等,則需要在不同字符集間轉換,只有客戶端作業系統字符集是資料庫字符集子集的基礎上才能正確轉換,否則會出現亂碼。
幾種常見情況分析
下面先看一個例子,再透過現象看本質,我們會針對這個例子進行分析。
該例子如下:
[sql] view plaincopyprint?- 1. 資料庫字符集為Unicode(UTF-8編碼)
- 我們的資料庫版本是10.2.0.4.0,資料庫字符集是:
- SQL> select * from v$nls_parameters where parameter='NLS_CHARACTERSET';
- PARAMETER VALUE
- ---------------------------------------- ------------------------------
- NLS_CHARACTERSET AL32UTF8
- 2. 客戶端作業系統字符集為內碼表936(字符集為ZHS16GBK)
- 可以使用chcp獲得windows的內碼表(code page)
- C:\Documents and Settings\a105024\Desktop>chcp
- Active code page: 936
- 3. 建立測試表
- SQL> createtable test(id number,var varchar2(30));
- Table created.
- 4. 插入資料
- 這裡在同一個作業系統啟動兩個session,session1的NLS_LANG設為和資料庫字符集一樣(即AL32UTF8):
- C:\Documents and Settings\a105024\Desktop>set nls_lang=Simplified Chinese_China.AL32UTF8
- 連線資料庫並插入一條資料:
- Session_1>insertinto test values(1,'中國');
- 1 row created.
- Session_1>commit;
- Commit complete.
- session2的NLS_LANG設為和客戶端作業系統一樣(即ZHS16GBK):
- C:\Documents and Settings\a105024\Desktop>set nls_lang=Simplified Chinese_China.ZHS16GBK
- 連線資料庫並插入一條資料:
- Session_2>insertinto test values(2,'中國');
- 1 row created.
- Session_2>commit;
- Commit complete.
- 5. 執行查詢
- 在session 1上執行查詢:
- Session_1>select * from test;
- ID VAR
- ---------- ---------------------
- 1 中國
- 2 涓 浗
- 在session 2上執行查詢:
- Session_2>select * from test;
- ID VAR
- ---------- --------------------
- 1 ???
- 2 中國
上面例子看起來很詭異,session1和2都能正常顯示自己插入的字串,又都不能正常顯示對方插入的字串。為了弄清楚,我們首先得知道資料庫裡對這兩個字串是怎麼儲存的。我們可以使用dump函式獲得字元在資料庫的編碼:
[sql] view plaincopyprint?
- SQL> select id,dump(var,1016) from test;
- ID DUMP(VAR,1016)
- -- ------------------------------------------------------------
- 1 Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
- 2 Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd
根據AL32UTF8的編碼,“中國”兩字的正確編碼為(都為3個位元組):
中--e4,b8,ad
國--e5,9b,bd
因此session 1插入的字串在資料庫中的編碼是錯誤的,session 2正確。這也是為什麼一定要設定NLS_LANG為客戶端作業系統的字符集。
但是根據上面實驗我們可以知道,資料庫中儲存正確,並不代表客戶端能正常顯示;同樣地,即時資料庫沒有正確儲存,有時候客戶端也能夠正常顯示,這又是為什麼呢?別急,請聽我慢慢道來:
場景1:session 1插入,session 1查詢,在資料庫中儲存錯誤,但顯示正確。
插入過程:
”中國“兩字在客戶端作業系統字符集ZHS16GBK中的編碼是”d6,d0,b9,fa",由於NLS_LANG和資料庫字符集相同,資料庫端對客戶端傳過來的字元編碼不進行任何轉換直接存入資料庫,因此資料庫中儲存的編碼也是“d6,d0,b9,fa”,
讀取過程:
資料庫端讀取的編碼是“d6,d0,b9,fa”,由於NLS_LANG和資料庫字符集相同,客戶端對資料庫端傳過來的字元編碼不進行任何轉換直接顯示,編碼”d6,d0,b9,fa“在客戶端作業系統字符集ZHS16GBK對應的漢字為“中國”。
從以上分析可知,雖然讀取時正確的,但那是因為負負得正,實際上資料庫中儲存是錯誤的,因此要特別小心這種情況,在生成庫中要避免。其實只要對它進行length操作就能輕易揭開它的假面具:
- Session_1>select length(var) from test where id=1;
- LENGTH(VAR)
- -----------
- 3
得出的長度居然為3!實際的長度只是2,這會帶來很多麻煩。
場景2:session 1插入,session 2查詢,在資料庫中儲存錯誤,顯示也錯誤。
插入過程和場景1一樣,這裡就不再累述。
讀取過程:
資料庫端讀取的編碼是“d6,d0,b9,fa”,由於NLS_LANG和資料庫字符集不同,客戶端對資料庫端傳過來的字元編碼進行轉換,資料庫端字符集AL32UTF8裡編為“d6,d0,b9,fa”無法在客戶端作業系統字符集ZHS16GBK裡找到對應的編碼,所以只好用?代替。
場景3:session 2插入,session 1查詢,在資料庫中儲存正確,但顯示錯誤。
插入過程:
”中國“兩字在客戶端作業系統字符集ZHS16GBK中的編碼是”d6,d0,b9,fa",由於NLS_LANG和資料庫字符集不同,Oracle會進行字元編碼轉換,也就是將字符集ZHS16GBK裡“中國”的編碼“d6,d0,b9,fa"轉換為字符集"AL32UTF8"裡”中國“的編碼”e4,b8,ad,e5,9b,bd“。
讀取過程:
資料庫端讀取的編碼是”e4,b8,ad,e5,9b,bd“,由於NLS_LANG和資料庫字符集相同,客戶端對資料庫端傳過來的字元編碼不進行任何轉換直接顯示,編碼”e4,b8,ad,e5,9b,bd“在客戶端作業系統字符集ZHS16GBK對應的漢字為“涓 浗”(原本2個字元,現在變成了3個字元,因為ZHS16GBK的漢字以2個位元組編碼)。
場景4:session 2插入,session 2查詢,在資料庫中儲存正確,顯示也正確。
插入過程和場景3類似。
讀取過程:
資料庫端讀取的編碼是”e4,b8,ad,e5,9b,bd“,由於NLS_LANG和資料庫字符集不同,客戶端對資料庫端傳過來的字元編碼進行轉換,資料庫端字符集AL32UTF8裡”中國“兩字的編碼”e4,b8,ad,e5,9b,bd“轉換成客戶端作業系統字符集ZHS16GBK裡“中國”兩字的編碼“d6,d0,b9,fa",並正常顯示。
這種情況雖然經過了兩次轉換,都確實最正確、最推薦的方式。
附錄:Oracle 字符集超集和子集的對應關係可檢視:http://download.oracle.com/docs/cd/B19306_01/server.102/b14225/applocaledata.htm#sthref1988
結論:NLS_LANG只和客戶端作業系統的字符集相關,如果客戶端作業系統的字符集和資料庫字符集間無法正確轉換,則應該首先改變客戶端終端的字符集,而不是簡單地把NLS_LANG設為和資料庫字符集一樣。
來源:http://blog.csdn.net/dbanote/article/details/9158367,作者:朱顯傑