Oracle數據庫中文亂碼問題

  最近碰到Oracle亂碼問題,剛開始甚是頭疼,之前在合肥出差的時候,這種問題也碰到過,當時直接拋給了「烏壓壓一片」(一個搞數據的同事兒),此次沒辦法躲過,只好硬着頭皮上。雖然我此次碰到的是Oracle亂碼問題中的一個,可是我決定將這個亂碼問題整理清楚(不整清楚,就以爲身邊有個定時炸彈,怕下次整數據庫的時候會忽然又爆炸)。html

  解決這個問題的關鍵在於理解字符集的概念,因此在正文開始以前,有必要先提一下字符集的相關知識!(這部分知識,對於解決j2ee中文參數傳遞過程當中出現的亂碼,也很是具備參考意義)

1、字符、字節和編碼(熟悉的人或者急於解決問題的人,直接跳過這一節。備註:此節內容非原創,查看原創請鏈接:        http://www.regexlab.com/zh/encoding.htm程序員

      1.1 字符與編碼的發展web

從計算機對多國語言的支持角度看,大體能夠分爲三個階段:sql

  系統內碼 說明 系統
階段一 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 個序號:session

2D 4E 87 65 31 00 32 00 33 00 00 00      ← 在 x86 CPU 中,低字節在前
             
1 2 3 \0  

一共佔 10 個字節。架構

  1.2 字符,字節,字符串oracle

理解編碼的關鍵,是要把字符的概念和字節的概念理解準確。這兩個概念容易混淆,咱們在此作一下區分:

  概念描述 舉例
字符 人們使用的記號,抽象意義上的一個符號。 '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 字符串」。

 2、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、解決數據庫亂碼原理特輯內容

3.1 前言

  在解決數據庫亂碼問題中,涉及到三個方面的字符集:一、oracel server端的字符集;二、oracle client端的字符集;三、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/

相關文章
相關標籤/搜索