最近碰到Oracle亂碼問題,剛開始甚是頭疼,以前在合肥出差的時候,這種問題也碰到過,當時直接拋給了“烏壓壓一片”(一個搞數據的同事兒),這次沒辦法躲過,只好硬着頭皮上。雖然我這次碰到的是Oracle亂碼問題中的一個,但是我決定將這個亂碼問題整理清楚(不整清楚,就覺得身邊有個定時炸彈,怕下次整數據庫的時候會突然又爆炸)。
一、字符、字節和編碼(熟悉的人或者急於解決問題的人,直接跳過這一節。備注:此節內容非原創,查看原創請連接: http://www.regexlab.com/zh/encoding.htm)
1.1 字符與編碼的發展
從計算機對多國語言的支持角度看,大致可以分為三個階段:
| 系統內碼 | 說明 | 系統 | |
| 階段一 | ASCII | 計算機剛開始只支持英語,其它語言不能夠在計算機上存儲和顯示。 | 英文 DOS |
| 階段二 | ANSI編碼 (本地化) |
為使計算機支持更多語言,通常使用 0x80~0xFF 范圍的 2 個字節來表示 1 個字符。比如:漢字 '中' 在中文操作系統中,使用 [0xD6,0xD0] 這兩個字節存儲。 不同的國家和地區制定了不同的標准,由此產生了 GB2312, BIG5, JIS 等各自的編碼標准。這些使用 2 個字節來代表一個字符的各種漢字延伸編碼方式,稱為 ANSI 編碼。在簡體中文系統下,ANSI 編碼代表 GB2312 編碼,在日文操作系統下,ANSI 編碼代表 JIS 編碼。 不同 ANSI 編碼之間互不兼容,當信息在國際間交流時,無法將屬於兩種語言的文字,存儲在同一段 ANSI 編碼的文本中。 |
中文 DOS,中文 Windows 95/98,日文 Windows 95/98 |
| 階段三 | UNICODE (國際化) |
為了使國際間信息交流更加方便,國際組織制定了 UNICODE 字符集,為各種語言中的每一個字符設定了統一並且唯一的數字編號,以滿足跨語言、跨平台進行文本轉換、處理的要求。 | Windows NT/2000/XP,Linux,Java |
字符串在內存中的存放方法:
在 ASCII 階段,單字節字符串使用一個字節存放一個字符(SBCS)。比如,"Bob123" 在內存中為:
| 42 | 6F | 62 | 31 | 32 | 33 | 00 |
| B | o | b | 1 | 2 | 3 | \0 |
在使用 ANSI 編碼支持多種語言階段,每個字符使用一個字節或多個字節來表示(MBCS),因此,這種方式存放的字符也被稱作多字節字符。比如,"中文123" 在中文 Windows 95 內存中為7個字節,每個漢字占2個字節,每個英文和數字字符占1個字節:
| D6 | D0 | CE | C4 | 31 | 32 | 33 | 00 |
| 中 | 文 | 1 | 2 | 3 | \0 | ||
在 UNICODE 被采用之后,計算機存放字符串時,改為存放每個字符在 UNICODE 字符集中的序號。目前計算機一般使用 2 個字節(16 位)來存放一個序號(DBCS),因此,這種方式存放的字符也被稱作寬字節字符。比如,字符串 "中文123" 在 Windows 2000 下,內存中實際存放的是 5 個序號:
| 2D | 4E | 87 | 65 | 31 | 00 | 32 | 00 | 33 | 00 | 00 | 00 | ← 在 x86 CPU 中,低字節在前 |
| 中 | 文 | 1 | 2 | 3 | \0 | |||||||
一共占 10 個字節。
1.2 字符,字節,字符串
理解編碼的關鍵,是要把字符的概念和字節的概念理解准確。這兩個概念容易混淆,我們在此做一下區分:
| 概念描述 | 舉例 | |
| 字符 | 人們使用的記號,抽象意義上的一個符號。 | '1', '中', 'a', '$', '¥', …… |
| 字節 | 計算機中存儲數據的單元,一個8位的二進制數,是一個很具體的存儲空間。 | 0x01, 0x45, 0xFA, …… |
| ANSI 字符串 |
在內存中,如果“字符”是以 ANSI 編碼形式存在的,一個字符可能使用一個字節或多個字節來表示,那么我們稱這種字符串為 ANSI 字符串或者多字節字符串。 | "中文123" (占7字節) |
| UNICODE 字符串 |
在內存中,如果“字符”是以在 UNICODE 中的序號存在的,那么我們稱這種字符串為UNICODE 字符串或者寬字節字符串。 | L"中文123" (占10字節) |
由於不同 ANSI 編碼所規定的標准是不相同的,因此,對於一個給定的多字節字符串,我們必須知道它采用的是哪一種編碼規則,才能夠知道它包含了哪些“字符”。而對於 UNICODE 字符串來說,不管在什么環境下,它所代表的“字符”內容總是不變的。
1.3 字符集與編碼
各個國家和地區所制定的不同 ANSI 編碼標准中,都只規定了各自語言所需的“字符”。比如:漢字標准(GB2312)中沒有規定韓國語字符怎樣存儲。這些 ANSI 編碼標准所規定的內容包含兩層含義:
1) . 使用哪些字符。也就是說哪些漢字,字母和符號會被收入標准中。所包含“字符”的集合就叫做“字符集”。
2) .規定每個“字符”分別用一個字節還是多個字節存儲,用哪些字節來存儲,這個規定就叫做“編碼”。
各個國家和地區在制定編碼標准的時候,“字符的集合”和“編碼”一般都是同時制定的。因此,平常我們所說的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”這層含義外,同時也包含了“編碼”的含義。
“UNICODE 字符集”包含了各種語言中使用到的所有“字符”。用來給 UNICODE 字符集編碼的標准有很多種,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。
1.4 常用的編碼簡介
簡單介紹一下常用的編碼規則,為后邊的章節做一個准備。在這里,我們根據編碼規則的特點,把所有的編碼分成三類:
| 分類 | 編碼標准 | 說明 |
| 單字節字符編碼 | ISO-8859-1 | 最簡單的編碼規則,每一個字節直接作為一個 UNICODE 字符。比如,[0xD6, 0xD0] 這兩個字節,通過 iso-8859-1 轉化為字符串時,將直接得到 [0x00D6, 0x00D0] 兩個 UNICODE 字符,即 "ÖÐ"。 反之,將 UNICODE 字符串通過 iso-8859-1 轉化為字節串時,只能正常轉化 0~255 范圍的字符。 |
| ANSI 編碼 | GB2312, BIG5, Shift_JIS, ISO-8859-2 …… |
把 UNICODE 字符串通過 ANSI 編碼轉化為“字節串”時,根據各自編碼的規定,一個 UNICODE 字符可能轉化成一個字節或多個字節。 反之,將字節串轉化成字符串時,也可能多個字節轉化成一個字符。比如,[0xD6, 0xD0] 這兩個字節,通過 GB2312 轉化為字符串時,將得到 [0x4E2D] 一個字符,即 '中' 字。 “ANSI 編碼”的特點: 1. 這些“ANSI 編碼標准”都只能處理各自語言范圍之內的 UNICODE 字符。 2. “UNICODE 字符”與“轉換出來的字節”之間的關系是人為規定的。 |
| UNICODE 編碼 | UTF-8, UTF-16, UnicodeBig …… |
與“ANSI 編碼”類似的,把字符串通過 UNICODE 編碼轉化成“字節串”時,一個 UNICODE 字符可能轉化成一個字節或多個字節。 與“ANSI 編碼”不同的是: 1. 這些“UNICODE 編碼”能夠處理所有的 UNICODE 字符。 2. “UNICODE 字符”與“轉換出來的字節”之間是可以通過計算得到的。 |
我們實際上沒有必要去深究每一種編碼具體把某一個字符編碼成了哪幾個字節,我們只需要知道“編碼”的概念就是把“字符”轉化成“字節”就可以了。對於“UNICODE 編碼”,由於它們是可以通過計算得到的,因此,在特殊的場合,我們可以去了解某一種“UNICODE 編碼”是怎樣的規則。
1.5 幾種誤解,以及亂碼產生的原因和解決辦法
| 對編碼的誤解 | |
| 誤解一 | 在將“字節串”轉化成“UNICODE 字符串”時,比如在讀取文本文件時,或者通過網絡傳輸文本時,容易將“字節串”簡單地作為單字節字符串,采用每“一個字節”就是“一個字符”的方法進行轉化。 而實際上,在非英文的環境中,應該將“字節串”作為 ANSI 字符串,采用適當的編碼來得到 UNICODE 字符串,有可能“多個字節”才能得到“一個字符”。通常,一直在英文環境下做開發的程序員們,容易有這種誤解。 |
| 誤解二 | 在 DOS,Windows 98 等非 UNICODE 環境下,字符串都是以 ANSI 編碼的字節形式存在的。這種以字節形式存在的字符串,必須知道是哪種編碼才能被正確地使用。這使我們形成了一個慣性思維:“字符串的編碼”。 當 UNICODE 被支持后,Java 中的 String 是以字符的“序號”來存儲的,不是以“某種編碼的字節”來存儲的,因此已經不存在“字符串的編碼”這個概念了。只有在“字符串”與“字節串”轉化時,或者,將一個“字節串”當成一個 ANSI 字符串時,才有編碼的概念。不少的人都有這個誤解。 |
第一種誤解,往往是導致亂碼產生的原因。第二種誤解,往往導致本來容易糾正的亂碼問題變得更復雜。
在這里,我們可以看到,其中所講的“誤解一”,即采用每“一個字節”就是“一個字符”的轉化方法,實際上也就等同於采用 iso-8859-1 進行轉化。因此,我們常常使用 bytes = string.getBytes("iso-8859-1") 來進行逆向操作,得到原始的“字節串”。然后再使用正確的 ANSI 編碼,比如 string = new String(bytes, "GB2312"),來得到正確的“UNICODE 字符串”。
二、Oracle中文亂碼解決正文
2.1 操作環境
操作系統:win7 64bit
數據庫:Oracle 10.2.0.4.0 64bit
PLSQL版本號:9.0.6
先說說小生遇到的兩個亂碼問題吧,問題一:某xxxx.sql文件,里面都是insert語句,並且文本編輯器打開文件查看,里面待插入的中文數據顯示正常,但是通過命令行,使用“@xxxx.sql”導入數據庫后,發現數據庫中的中文數據都是“?????”這種形式;問題二:在確保導入數據庫中的中文數據正常的前提下,用plsql查看數據庫中的數據,plsql中文顯示為“??????”。
2.2 解決方案如下
針對問題一:
修改注冊表:HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\KEY_OraDb10g_home1\NLS_LANG,其值修改成:SIMPLIFIED CHINESE_CHINA.ZHS16GBK
針對問題二:
修改系統環境變量NLS_LANG的值(如果沒有就新建一個):SIMPLIFIED CHINESE_CHINA.ZHS16GBK
三、解決數據庫亂碼原理特輯內容
3.1 前言
在解決數據庫亂碼問題中,涉及到三個方面的字符集:1、oracel server端的字符集;2、oracle client端的字符集;3、dmp文件的字符集(只有在需要往數據庫里面導入dmp文件的時候會涉及到這點)。
3.2 知識儲備
查看Oracle客戶端字符集:
SELECT * FROM V$NLS_PARAMETERS;--返回當前用戶環境中設置的字符集
SELECT USERENV('language') FROM DUAL;--返回當前會話使用的字符集
特別說明:客戶端的字符集要求與服務器一致,才能正確顯示數據庫的非Ascii字符,如果多個設置存在的時候,NLS作用優先級別:Sql function > alter session > 環境變量>注冊表>參數文件> 數據庫默認參數
查看Oracle服務端字符集:
SELECT * FROM NLS_DATABASE_PARAMETERS;--返回Oracle server端的字符集,來源於props$,是表示數據庫的字符集
查看dmp文件的字符集:
用oracle的exp工具導出的dmp文件也包含了字符集信息,dmp文件的第2和第3個字節記錄了dmp文件的字符集。如果dmp文件不大,比如只有幾M或幾十M,可以用UltraEdit打開(16進制方式),看第2第3個字節的內容,如0354,然后用以下SQL查出它對應的字符集:
“select nls_charset_name(to_number('0354','xxxx')) from dual;”
如果dmp文件很大,比如有2G以上(這也是最常見的情況),用文本編輯器打開很慢或者完全打不開,可以用以下命令(在unix主機上):
“cat exp.dmp |od -x|head -1|awk '{print $2 $3}'|cut -c 3-6”
然后用上述SQL也可以得到它對應的字符集
NLS_LANG參數格式:
Language_Territory.Clientcharacterset
其中,Language顯示oracle消息,校驗,日期命名,Territory指定默認日期、數字、貨幣等格式Clientcharacterset指定客戶端將使用的字符集。比如NLS_LANG=AMERICAN_AMERICA.US7ASCII。AMERICAN是語言,AMERICA是地區,US7ASCII是客戶端字符集。在解決客戶端和服務端編碼不一致問題導致的亂碼時,Language和Territory可以不一致,但是Clientcharacterset必須一致。
其他數據庫字符集查詢相關語句:
實例字符集環境
SELECT * FROM NLS_INSTANCE_PARAMETERS;--顯示由參數文件init.ora定義的參數
主要涉及NLS_LANGUAGE、NLS_TERRITORY的值. NLS_INSTANCE_PARAMETERS其來源於v$parameter,注意:網上很多資料都說"NLS_INSTANCE_PARAMETERS 表示客戶端的字符集的設置,可以是參數文件,環境變量或者是注冊表",而且網上都人人亦雲。記住它是表示實例的字符集環境
數據庫可用字符集參數設置
SELECT * FROM V$NLS_VALID_VALUES
會話字符集環境
SELECT * FROM NLS_SESSION_PARAMETERS;
它來源於v$nls_parameters,表示會話自己的設置,可能是會話的環境變量或者是ALTER SESSION完成,如果會話沒有特殊的設置,將與 V$NLS_PARAMETERS一致
Oracle數據傳遞編碼/轉碼過程:(原文鏈接:http://blog.163.com/jiankun_liu/blog/static/1863927762013698175289/)
首先,要說清楚Oracle字符集的相關問題,則要先理清數據庫運行過程中的架構以及在這個架構中的字符集設置及這些設置之間的關聯關系,先畫一張圖看一下:

在這個圖中,為了說明問題,我們將服務器與客戶端分開,客戶端用應用程序比如sqlplus或者PL/SQL與服務端相連。
服務端有兩個字符集:服務端操作系統字符集(4)、服務端數據庫字符集(1);
客戶端有一個字符集:客戶端操作系統字符集(2);
客戶端有一個參數:操作系統參數NLS_LANG(1)。
這三個字符集與一個參數中,有一個字符集對整個架構的運行沒有影響,它就是服務端操作系統字符集(4),所以這個字符集將不再出現在我們的討論過程中。
為什么這個服務端操作系統字符集沒有用呢?這是因為Oracle在存取字符時與客戶端進行字符集確認與轉碼的過程是由Oracle數據庫自身完成的,不需要經過Oracle 數據庫所在的服務器的幫助。具體的是怎么回事用以下例子說明一下。
比如在Oracle數據庫中有一個表,用如下語句創建:
create table test(name varchar2(10));
為了說明問題假定有這樣的一個環境:服務器端Oracle數據庫的字符集是UTF8,客戶端操作系統字符集是ZHS16GBK,客戶端NLS_LANG參數設置為ZHS16GBK。那么從客戶端應用程序(比如sqlplus)發出這樣一條命令:
insert into test (name) values('中國');
首先,這里有一個字符串“中國”,客戶端操作系統用ZHS16GBK對它進行編碼,比如編成“167219”,並把它交給sqlplus程序,然后把它發送給Oracle數據庫。接着,Oracle數據庫收到一串編碼“167219”,不是直接往數據庫里一扔就完事的,它要問客戶端操作系統:“請問你給我的這串代碼是用什么格式編碼的啊?”客戶端操作系統怎么回答?它會這么回答:“編碼格式請參照參數NLS_LANG”。Oracle數據庫一看,NLS_LANG='ZHS16GBK',這個編碼格式與Oracle數據庫自身的編碼格式“UTF8”不一樣,然后就是Oracle數據庫發揮自己專長的地方了,為什么呢?因為Oracle數據庫有它自己的編碼表,而且不是一張而是好多張編碼表,它可以根據編碼表對編碼進行翻譯和轉碼。這就好比Oracle數據庫是一個翻譯,它會好幾國語言,牛人一個。像上面的這個情況,Oracle會把“167219”這串代碼拿過來,根據參數NLS_LANG查ZHS16GBK編碼表,找到對應這串代碼的字符“中國”,然后再到UTF8編碼表中查“中國”對應的編碼,比如查到的結果是“3224678”。
最后,將轉碼之后的編碼“3224678”存放到Oracle數據庫中去。
為了進一步說明問題,我們再執行一條語句:
select name from test;
首先,Oracle數據庫會從數據庫中取出一串代碼“3224678”。
接着,Oracle數據庫不是直接把這串代碼交給sqlplus程序,它會多問一句:“代碼串我是取出來了,它是UTF8編碼格式的,請問sqlplus,你希望要什么編碼格式的?”,sqlplus仍然會很爽快地告訴Oracle數據庫:“編碼格式請繼續參照參數NLS_LANG”。Oracle數據庫一看,ZHS16GBK跟UTF8又不一樣,所以先查UTF8編碼表,找到編碼“3224678”對應的字符“中國”,再查ZHS16GBK編碼表,找到“中國”對應的編碼“167219”,然后就是把最后得到的這串編碼“167219”交給sqlplus程序。
最后,sqlplus直接把得到的這串編碼扔給客戶端操作系統,而操作系統只有ZHS16GBK編碼表,它不會問得到的這串編碼是什么格式的,只會直接到ZHS16GBK編碼表中去查“167219”對應的字符是什么,並把它交給應用程序顯示出來。這個顯示的結果是“中國”。
以上就是一個完整的從客戶端編碼並經過Oracle數據庫轉碼存入數據庫,然后從數據庫取出並轉碼交給客戶端顯示的實驗。
從以上過程我們可以得出以下一些結論:
1.對Oracle數據庫存取起作用的是這些:客戶端操作系統字符集、客戶端操作系統參數NLS_LANG、服務端數據庫字符集。
2.對Oracle數據庫不起作用的是服務端操作系統字符集。
3.客戶端操作系統只有一張編碼表,與客戶端字符集對應。
4.Oracle數據庫的字符集只有一個,並且固定,一般不改變。
5.存放在Oracle數據庫中的字符串的編碼格式只有一個,它就是數據庫的字符集所對應的編碼格式。
6.Oracle數據庫有很多張編碼表,可以在數據存入時將其它編碼格式的編碼轉換為數據庫字符集指定的格式,取出時從數據庫字符集指定的格式轉換為其它編碼格式
7.整個架構中的轉碼只發生在Oracle數據庫邊界上,其它地方沒有。
8.Oracle是根據客戶端操作系統的參數NLS_LANG與自己的字符集進行對照來確定是否需要進行轉碼的。
最最重要的結論出來了:
9.Oracle數據庫如何選擇字符集?只有一個原則,那就是這個字符集要包含數據庫運行過程中所能存入的數據字符,通常作為中國人我們選擇ZHS16GBK,如果想再保險一點,選擇AL32UTF8。
10.服務器操作系統選擇什么字符集?這個字符集與數據庫字符集一點關系都沒有,只跟誰有關?操作系統管理員!所以它的選擇原則是,系統管理員想選擇什么就選擇什么。
11.客戶端操作系統選擇什么字符集?我是中國人,我用中文操作系統,所以我選擇ZHS16GBK。建議中國人都選擇ZHS16GBK。
12.客戶端操作系統參數NLS_LANG參數如何設置?這個只有一個設置方法,那就是與操作系統字符集相同。要不然會出問題的……
最最最最重要的一句話:
最好的,最不容易出字符集錯誤的就是:將數據庫字符集、客戶端字符集、客戶端操作系統NLS_LANG參數三個地方作同樣的設置。
另外再記錄一下EXP和IMP過程與字符集相關的事情。
EXP時,起作用的有Oracle數據庫的字符集和客戶端操作系統參數NLS_LANG兩項,這時服務器與客戶端操作系統字符集都不起作用。如果客戶端操作系統參數NLS_LANG與Oracle數據庫的字符集相同,那就直接導出,不需要轉碼,並且導出文件的字符集與上述兩項一樣;如果客戶端操作系統參數NLS_LANG與Oracle數據庫的字符集不同,那么導出時Oracle數據庫會將數據文件從Oracle數據庫的字符集編碼格式轉碼成客戶端操作系統參數NLS_LANG指定的編碼格式。總而言之一句話:導出文件的字符集格式與導出客戶端操作系統參數NLS_LANG一定相同。
IMP時,起作用的仍然是兩項,一項是DMP文件第二第三字節指定的字符集,另外一項是Oracle數據庫的字符集。兩個相同就不需要轉碼,兩個不同就轉成Oracle數據庫字符集指定的編碼格式。
相信看完以上文章定會對Oracle亂碼問題的產生原因,有個深刻的認識!當然,因為精力有限,有些轉載內容沒有實質性校驗正確,這邊只做記錄備忘之用,希望能對看官有所幫助~如有錯誤之處,還望批評指正,轉載請注明出處。本文很多內容非原創,尊重原創,參考網址如下:
http://blog.csdn.net/jovitang/article/details/5174062
http://blog.itpub.net/9240380/viewspace-666071
http://www.cnblogs.com/kerrycode/p/3749085.html
http://www.itpub.net/thread-235335-1-1.html
http://www.askmaclean.com/archives/script-list-nls-parameters-and-timezone.html
http://blog.itpub.net/12131609/viewspace-701225/
http://www.cnblogs.com/wanglibo/articles/2121098.html
http://www.itpub.net/thread-1012118-1-1.html
http://zhidao.baidu.com/link?url=AFYAsfP-7Dg1tS6PNGFrSkJRAnTSov34_8V7Pd2ujbKtsz4X8txKQFyIE4TRIVhjEDHakM-vGln58DHKAIq3j_
http://www.cnblogs.com/cubean/archive/2009/09/24/1573396.html
http://wenku.baidu.com/link?url=rWu8LKOCGdbGZ2kTzvGKzwkJQpUHwFufDNdaR7NGkG2ezpKe5JJLrkIlTppX9xQN9VquV_mXc6WtsfGskTeRHc3Fj2fXPaZDKpcv4RGUsD7
http://blog.csdn.net/meteorlwj/article/details/8057470
http://zhidao.baidu.com/link?url=toKCPyOUL5v9fx_wvSvO3P3TC1CBtwkKendU8b1CAzAPvEa5BWQB0GuVkiXceHY6Va-d9VrIbTFgMjw3WQG4GiqklSlEPgPSsdqYUddEzrm
http://www.educity.cn/shujuku/1176316.html
http://blog.163.com/jiankun_liu/blog/static/1863927762013698175289/
