轉自:http://www.cnblogs.com/morvenhuang/archive/2011/11/11/2245410.htmlhtml
第一部分字符集與編碼常識
sql
字符集:數據庫
人們根據須要把某些字符收集到一處,並賦以名稱,因而便有了某某字符集。服務器
編碼:網絡
當前面收集的工做完成之後,爲了讓只認識數字的「愚蠢」的計算機也可以存儲字符,人們不得不爲集合裏的每個字符分配」身份證號碼」,這就是編碼,今後,終於能夠以存儲編碼的方式在計算機中存儲字符了。oracle
在字符集與編碼世界的漫漫歷史長河裏(僞),出現過若干個讓計算機工做者們如雷貫耳的名字,這些名字,有些已經成了浮雲飄散了,有些還在咱們的代碼中折騰。ide
ASCII:函數
ü ASCII字符集:包含大小寫英文、阿拉伯數字、標點,以及一些不可見的控制符共128個。工具
ü ASCII編碼:使用7位表示一個字符。編碼範圍是[0-127](即Hex[00-7F]),其中[0-31](Hex[00-1F])部分以及127(Hex7F)是控制符,其他的都是些可見字符。post
GB2312:
ü GB2312字符集:ASCII字符集+7000左右漢字字符。
ü GB2312編碼:兼容ASCII編碼。對字節進行判斷,如值<=127,則意義等同於ASCII編碼;如值>127,則它須要跟其後的另外一個字節合併表示一個字符。其理論漢字編碼空間爲128X256,超過3萬個字符。
GBK:
ü GBK字符集:GB2312字符集+20000左右漢字字符。
ü GBK編碼:兼容GB2312編碼。利用了GB2312編碼閒置的編碼空間。
GB18030:
ü GB18030字符集:GBK字符集+若干漢字+若干少數民族字符,爲目前國內最新的字符集。
ü GB18030編碼:兼容GBK編碼。繼續利用GBK編碼閒置的編碼空間,對於超出編碼空間的則採用4個字節表示。
BIG5:
ü BIG5字符集:ASCII字符集+13000左右漢字(繁體)。
ü BIG編碼:兼容ASCII編碼。其編碼模式相似於GB2312.
UNICODE:(UNICODE一詞在平常使用中顯得寬泛、混亂,在不一樣的語境中能夠是如下意思之一。)
ü UNICODE標準:由一些組織提出的一套標準,對人類文字的顯示、編碼等進行了一系列的規定。
ü UNICODE字符集:目前最新版的UNICODE字符集中已經包含各類語言的超過10萬的字符。
ü UNICODE編碼:(狹義的UNICODE編碼可能指UCS-2,也可能指UTF-16;廣義的UNICODE編碼能夠指包括如下四種在內的若干種對UNICODE標準的編碼實現。)
1. UTF-32編碼:固定使用4個字節來表示一個字符,存在空間利用效率的問題。
2. UTF-16編碼:對相對經常使用的60000餘個字符使用兩個字節進行編碼,其他的(即’補充字符supplementary characters’)使用4字節。
3. UCS-2編碼:是對UNICODE早期版本的實現,它與UTF-16的惟一區別是它不包括’補充字符’,因此它對字符的編碼只使用兩個字節。目前此編碼模式已過期。
4. UTF-8編碼:兼容ASCII編碼;拉丁文、希臘文等使用兩個字節;包括漢字在內的其它經常使用字符使用三個字節;剩下的極少使用的字符使用四個字節。
ISO8859-1:(使用Oracle的同志們可能見過這個WE8ISO89859P1,沒錯,就是它。)
ü ISO8859-1字符集:ASCII字符集+若干西歐字符,例如字母Â、Ë。
ü ISO8859-1編碼:使用8位表示一個字符,同時移除了原ASCII編碼中的控制符(即[0-31],及127)。
Code page:(能夠把」code page」認爲是」編碼」的近義詞。至於爲何有這個名稱?歷史遺留問題。)
ü ANSI code pages:你必定見過ANSI,想一想另存文本文件時。ANSI code pages其實是一系列的編碼集合,根據操做系統區域設置而激活其中一種做爲默認ANSI編碼。例如公司電腦(英文系統)上的ANSI code page多是1252,而家裏的中文系統則多是936。因此在家裏能夠用ANSI存儲一個包含中文的文本文件,在公司則不行。能夠在註冊表鍵:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\NLS\CodePage\ACP中查看到當前使用的ANSI code page。 C#能夠經過Encoding.Default查看。
ü OEM code pages: OEM code pages是給控制檯應用程序(如SQLPLUS)使用的。除CJK環境(Chinese-Japanese-Korean)外,Windows使用不一樣的ANSI code page和OEM code page。例如,公司英文系統上使用的是437。可使用CHCP命令查看當前使用的OEM code page, C#能夠經過Console.OutputEncoding查看。
Code page 1252:
ü cp1252字符集:ASCII字符集+若干西歐字符+若干特殊符號,好比™、‰.
ü cp1252編碼:使用8位表示一個字符。編碼範圍是[0-255](即Hex[00-FF]),[0-127]部分與ASCII相同,新增的大部分是西歐的字符,例如一些帶上標的字母Â、Ë,以及像這樣一類特殊符號)
PS1:現實中兩臺PC上的code page信息
PC 1:英文版Windows XP,ANSI code page=1252, OEM code page=437
PC 2:中文版Windows 7,ANSI code page=936, OEM code page=936
PS2:cp1252與cp437編碼表下載請猛擊這裏,早期控制檯應用程序經常須要畫一些粗糙的表格等等圖形,因此能夠在437中看到很多不一樣的橫線豎線這一類的特殊符號。
PS3:CP125二、ISO8859-一、ASCII比較,就實際使用的編碼範圍來講:CP1252>ISO8859-1>ASCII。ASCII是[0-127],CP1252是[0-255],ISO8859-1則移除了cp1252中[0-31]及127這些不可見的控制符,同進移除了[128-159](即Hex[80-9F])中的特殊符號。
第二部分 Oracle中的編碼與字符集
1.爲何須要兩個字符集?
Oracle中有兩個字符集:
1)數據庫字符集
2)國家字符集
爲何要有兩個字符集?若是我知道只須要英文,設置數據庫字符集=US7ASCII,若是我知道只須要西歐字符,設置數據庫字符集=WE8MSWIN1252或者WE8ISO89859P1,或者乾脆就用AL32UTF8。你看,我只須要設定「數據庫字符集」,那麼「國家字符集」有什麼必要呢?
其實,考慮到歷史遺留問題以及數據庫建立者們沒法避免的「短視」,不少現有數據庫都沒法支持UNICODE字符集,例如要在現有的US7ASCII數據庫字符集的數據庫中存儲中文,這個時候「國家字符集」+NVARCHAR2這樣的組合就能救你一命了。對於數據類型爲NVARCHAR2(以及NCHAR, NCLOB)的字段,它使用是國家字符集,與數據庫字符集的設置無關。自9i之後,國家字符集可選的只有AL16UTF16與AL32UTF8,UTF-16與UTF-8都是UNICODE編碼標準的實現,因些能夠表示世界上幾乎全部的文字。
固然,若是數據庫字符集自己就使了UNICODE字符集,就沒有必要使用NVARCHAR2, NCHAR, NCLOB這些類型了。
2.字符集名稱的玄機
Oracle對字符集的命名實際上有必定的規則可尋,例如:
AL32UTF8
【AL】支持全部語言(All Language)。
【32】每字符最多佔用32位(4字節)。
【UTF8】編碼爲UTF-8。
WE8MSWIN1252
【WE】支持西歐語言(Western Europe)。
【8】每字符須要佔用8位(單字節)。
【MSWIN1252】編碼爲CP1252。
US7ASCII
【US】表示美國(United States)。
【7】每字符須要佔用7位。
【ASCII】編碼爲ASCII。
其它如ZHS16GBK,ZHT16BIG5,US8PC437(編碼爲OEM cp437),均可以類推。
3.例子很重要
3.1.準備兩個數據庫
上帝說要有例子,因而有了兩個相同版本的數據庫,A跟B:
3.2.工具很重要
在測試以前,爲避免工具自己的特性給人形成的困惑,介紹一下幾個客戶端工具對UNICODE 的支持狀況:
ü SQLPLUS:不支持UNICODE字符集。是否支持中文取決於當前的OEM code page,若是是cp437,不管輸入仍是顯示中文都是不可能的。但若是是cp936,則能夠支持中文輸入輸出。
ü PLSQL Developer:7.0版本的查詢結果窗口支持UNICODE字符集,可是編輯窗口(即輸入SQL語句的窗口)不支持。8.0版徹底支持UNICODE。
ü Oracle SQL Developer:查詢結果窗口與編輯窗口都支持UNICODE字符集。
3.3.出現亂碼了
這裏使用Oracle SQL Developer,分別在A、B中插入並查詢中文:
暫時先跳過VARCHAR2字段,先來關注NVARCHAR2字段,爲何在A庫不能正常顯示?無非有這幾種可能:
ü 客戶端操做系統不支持顯示中文。
ü Oracle客戶端工具(這裏是Oracle SQL Developer)不支持顯示中文。
ü Oracle客戶端有相關設置(好比NLS_LANG)不正確。
ü 存儲在數據庫中的數據已是不正確的數據。
第一點,客戶端操做系統是否支持中文對運行於其上的應用程序有影響嗎?應該有兩種狀況,一種是應用程序依賴於操做系統的中文支持;另外一種是有一些軟件本身帶有語言包及字體(好比Adobe的一些產品,.NET程序在編譯的時候也能夠選擇將字體文件打包進去),那麼它應該不依賴於操做系統。
我猜想Oracle SQL Developer應該是屬於前一種,同時我檢查了操做系統,肯定其已經支持東亞語言(Control panel—Regional and language options—Language tab—Supplemental languages support—Install files for East Asian languages,若是checkbox已經選中,說明已經安裝東亞語言包)。
第二點,不管查詢結果窗口仍是編輯窗口都支持UNICODE字符集。
第三點,因爲不依賴於Oracle client的OCI,客戶端註冊表中的NLS_LANG設置對像Oracle SQL Developer沒有影響。
第四點,咱們藉助DUMP()函數來肯定NVARCHAR2字段中具體的內容。
DUMP()的語法:DUMP(<value>[,<format>[,<offset>[,<length>]]])
其中的format參數:若是是8則表示結果使用8進製表示,若是是16則表示16進制,若是是0到16間的其它數則都使用10進制。若是是大於16的數,則分幾種狀況:若是是可見的ASCII字符則直接打印此字符,若是是控制字符則打印成「^x」,其它狀況則把結果按16進制顯示。爲format加上1000則表示除了輸出結果以外,還會附帶輸出所使用的字符集信息。
這裏咱們使用:
咱們知道「中」字的UTF-16編碼是4E2D,顯然在A庫中存儲的數據已是不對的,00BF實際上就是一個倒的問號字符,其存儲在數據庫中的原始數據已經不對了,更況且是客戶端的顯示。
3.4.找不一樣
那麼爲何兩個庫會不同呢?嫌疑很快就落在了數據庫字符集上,由於A和B的區別只在數據庫字符集上,一個是WE8MSWIN1252,另外一個是AL32UTF8。通過測試,結論是:
Oracle SQL Developer忽略NLS_LANG,字符串直接以照數據庫字符集進行編碼後由客戶端傳輸到服務器端。因爲A庫數據庫字符集不支持漢字,很不幸地被替換成了默認的BF並最終被存儲到數據庫中,永遠地錯下去。B庫則相反,中文在傳輸的過程當中「存活」下來併成功到達服務器端,最終自動轉換成NVARCHAR2所用的編碼並存儲到庫中。
3.5.如何讓NVARCHAR2字段工做
看起來彷佛A庫中的NVARCHAR2字段永遠也沒法正常使用了,並不是這樣,對於Oracle SQL Developer,經過一些設置,就可讓NVARCHAR2能夠正常地插入、查詢。
找到{ORACLE_HOME}\sqldeveloper\sqldeveloper\bin\sqldeveloper.conf(依賴於你的Oracle SQL Developer安裝路徑),添加一行配置:
AddVMOption -Doracle.jdbc.convertNcharLiterals=true
同時在中文字符串前添加「N」前綴:
這個配置起到的做用是這樣的:在INSERT語句從客戶端傳輸到服務器端以前,Oracle SQL Developer檢測(其實是JDBC檢測)語句,若是發現「N」前綴,則事先將這部分的字符串按UTF-16進行編碼獲得16進制串。也就是至關於執行了這個命令:
INSERT INTO charset_test VALUES(2,’中’,UNISTR('\4e2d'));
C#不須要作特殊的配置來讓NVARCHAR2正常工做,只須要在執行INSERT時使用參數並選擇正確的參數類型選:
4.客戶端的NLS_LANG設置及編碼轉換
前面我說過Oracle SQL Developer忽略客戶端NLS_LANG設置,那麼對於其它的工具呢?(這裏咱們主要關注字符集及編碼,不討論NLS_LANG對日期格式、排序方式、數字顯示格式等等的影響)
ü SQLPLUS,插入與查詢都依賴於客戶端NLS_LANG設置。一般,客戶端NLS_LANG設置要與當前的OEM Codepage一致,好比US8PC437。
ü PL/SQL Developer,插入與查詢都依賴於客戶端NLS_LANG設置。一般,客戶端NLS_LANG設置要與數據庫字符集一致。
使用SQLPLUS能夠清晰地看到Oracle編碼轉換的過程:
1) 在Oracle客戶端向服務器端提交SQL語句時,Oracle客戶端根據NLS_LANG和數據庫字符集,對從應用程序接傳送過來的字符串編碼進行轉換處理。若是NLS_LANG與數據庫字符集相同,不做轉換,不然要轉換成數據庫字符集並傳送到服務器。服務器在接收到字符串編碼以後,對於普通的CHAR或VARCHAR2類型,直接存儲;對於NCHAR或NVARCHAR2類型,服務器端將其轉換爲國家字符集再存儲。
2) 在查詢數據時,服務器端原樣返回存儲在庫中的數據,由客戶端根據返回的元數據中的字符集信息與NLS_LANG和NLS_NCHAR的設置進行比較(若是NLS_NCHAR沒有設置,則其默認值爲NLS_LANG中的字符集設置),若是元數據中的字符集信息與客戶端設置一致,不進行轉換,不然要進行轉換。國家字符集的轉換根據NLS_NCHAR設置進行轉換。
這裏我也舉幾個使用SQLPLUS的測試例子,分別在A、B兩庫執行相同的語句,而後經過網絡抓包查看從Oracle client傳輸到服務器的具體內容。
例1 客戶端NLS_LANG:WE8MSWIN1252
SQL命令:insert into charset_test values(1,'æ',null);
網絡抓包(A庫,數據庫字符集爲WE8MSWIN1252):91
解釋:因爲應用程序(即SQLPLUS)使用的編碼是Codepage437,因此æ的編碼是91。當91被傳給Oracle client後,Oracle client根據NLS_LANG誤判其使用的編碼是Codepage1252,又因爲NLS_LANG設置與數據庫字符集一致,因而Oracle client不進行編碼轉換,91被直接傳給服務器並存儲,考慮到數據庫字符集是Codepage1252,很顯然91是錯誤的數據(字符[æ]在Codepage1252下的編碼是E6,而非91)。
這個錯誤致使了一個有趣的現象,那就是在同一個客戶端使用SQLPLUS查詢竟然能夠看到正確字符[æ],這是因爲SELECT的時候91也被直接返回,而且在Oracle client也不進行編碼轉換而是直接傳給了應用程序,恰巧應用程序根據本身使用的編碼能夠正確解析91。可是換一個客戶端機器,或者換一個客戶端工具均可能獲得不同的查詢結果。
網絡抓包(B庫,數據庫字符集爲AL32UTF8):E2 80 98
解釋:因爲應用程序(即SQLPLUS)使用的編碼是Codepage437,因此æ的編碼是91。當91被傳給Oracle client後,Oracle client根據NLS_LANG誤判其使用的編碼是Codepage1252,而91在Codepage1252中對應的是字符[‘],根據Codepage1252到數據字符集UTF8的轉換,最終轉換成了E2 80 98,即UTF8下的[‘]。
例2客戶端NLS_LANG:US7ASCII
SQL命令:insert into charset_test values(1,'æ',null);
網絡抓包(A庫):BF
解釋:因爲應用程序(即SQLPLUS)使用的編碼是Codepage437,因此æ的編碼是91。當91被傳給Oracle client後,Oracle client根據NLS_LANG誤判其使用的編碼是ASCII,而91在ASCII中是無效編碼,根據ASCII到數據字符集Codepage1252的轉換,最終轉換成了BF,BF是Codepage1252遇到無效編碼時使用的默認替換編碼。
網絡抓包(B庫): EF BF BD
解釋:因爲應用程序(即SQLPLUS)使用的編碼是Codepage437,因此æ的編碼是91。當91被傳給Oracle client後,Oracle client根據NLS_LANG誤判其使用的編碼是ASCII,而91在ASCII中是無效編碼,根據ASCII到數據字符集UTF8的轉換,最終轉換成了EF BF BD,EF BF BD是UTF8遇到無效編碼時使用的默認替換編碼。
例3客戶端NLS_LANG:US8PC437
SQL命令:insert into charset_test values(1,'æ',null);
網絡抓包(A庫):E6
解釋:E6是字符[æ]的正確的Codepage1252編碼,這次因爲應用程序(即SQLPLUS)使用的是Codepage437,Oracle client從NLS_LANG得到的編碼信息也是Codepage437,因而進行了正確的編碼轉換。
網絡抓包(B庫):C3 A6
解釋:C3 A6是字符[æ]的正確的UTF8編碼,這次因爲應用程序(即SQLPLUS)使用的是Codepage437,Oracle client從NLS_LANG得到的編碼信息也是Codepage437,因而進行了正確的編碼轉換。
我以爲,只有SQLPLUS的日子老是那麼美好,一切看起來既合理又可解釋。當其它工具出現以後,世界就變得一團亂麻了,Oracle SQL Developer徹底忽略客戶端NLS_LANG設置卻是讓事情變得簡單,不過PL/SQL Developer則是另外一回事,我花了4天時間企圖搞明白其中的編碼轉換過程,最終只證實它就是個不可理喻的玩意兒,惟一目前看起來還正確的結論是:若是要用PL/SQL Developer,只好仍是把NLS_LANG設置得跟數據庫字符集一致。其它就只能自求多福了。
5.NLS_LANG對ODP.NET的影響
惟一受客戶端NLS_LANG影響的是OracleString的GetNonUnicodeBytes()方法,此方法依賴於客戶端本地設置的字符集,例如咱們把NLS_LANG從AMERICAN_AMERICA.WE8MSWIN1252改爲AMERICAN_AMERICA.US7ASCII
其中230(即HexE6)正是字符‘æ’的編碼,而63(即Hex3F)是ASCII中的問號(因爲ASCII字符集中沒有‘æ’,故用問號代替)。
6.關於VARCHAR2, NVARCHAR2的其它問題
NVARCHAR2(N),其中的N是指字符數,不是字節數。不過其最大長度是以字節爲單位,即4000字節。
VARCHAR2(N),其中的N多是指字符數,也多是指字節數。你能夠顯式地在聲明的時候指定,好比VARCHAR2(10 BYTE)或者VARCHAR2(10 CHAR),未顯式指明時,則由參數NLS_LENGTH_SEMANTICS決定。須要注意的是你能成功聲明VARCHAR2(4000 CHAR)並不能保證你能真的存儲4000個字符,若是超過4000字節,該報錯Oracle仍是會報錯。
【參考及引用】:
1. http://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html
2. http://www.laoxiong.net/category/oracle/orainternal/page/2
3. http://en.wikipedia.org/wiki/Windows-1252
4. http://en.wikipedia.org/wiki/ASCII
5. http://en.wikipedia.org/wiki/Code_page_936
6. http://en.wikipedia.org/wiki/Code_page_437
7. http://en.wikipedia.org/wiki/UTF-8
8. http://en.wikipedia.org/wiki/UTF-16/UCS-2
9. http://en.wikipedia.org/wiki/UTF-32/UCS-4
10. http://en.wikipedia.org/wiki/GB_18030
11. http://en.wikipedia.org/wiki/Unicode
12. OReilly Oracle PL SQL Programming 5th Edition,Steven Feuerstein, Bill Pribyl