做爲一個ORACLE DBA,在工做中會常常處理因爲字符集產生的一些問題。可是當真正想寫一些這方面的東西時,卻忽然又沒有了頭緒。發了半天呆,仍是決定用兩個字符集方面的例子做爲切入點,倒不失爲一個頭緒,說不定在實驗的過程當中,問題就會一個接着一個的浮現出來。php
如今,讓咱們切入正題。html
我用的數據庫是oracle10.2.0.3,數據庫字符集是al32utf8。sql
客戶端就是同一臺機器的windows xp.數據庫
下面是演示的例子:windows
[php]session
SQL> drop table test purge;oracle
Table dropped.ide
SQL> create table test(col1 number(1),col2 varchar2(10));函數
Table created.測試
[/php]
--session 1 設置客戶端字符集爲 zhs16gbk(修改註冊表nls_lang項的characterset 爲zhs16gbk) 向表中插入兩個中文字符。
[php]
SQL> insert into test values(1,'中國'); --1爲session 1的標記
1 row created.
SQL> commit;
Commit complete.
[/php]
--session 2 設置客戶端字符集 al32utf8(修改註冊表nls_lang項的characterset 爲al32utf8),與數據庫字符集相同。 向表中插入兩個和session 1相同的中文字符。
[php]
SQL> insert into test values(2,'中國'); --2爲session 2的標記
1 row created.
SQL> commit;
Commit complete.
--session 1
SQL> select * from test;
COL1 COL2
---------- --------------------
2 ???
1 中國
--session 2
SQL> select * from test;
COL1 COL2
---------- ----------
2 中國
1 涓浗
[/php]
從session 1和session 2的結果中能夠看到,相同的字符(注意,我指的是咱們看到的,顯示爲相同的字符),在不一樣的字符集輸入環境下,顯示成了亂碼。
在zhs16gbk字符集的客戶端,咱們看到了utf8字符集客戶端輸入的相同的中文變成了亂碼-->col1=2的col2字段
在utf8字符集客戶端,咱們看到zhs16gbk字符集的客戶端輸入的中文變成了另外的字符 -->col1=1的col2字段
從這個例子裏,咱們好像感受到出了什麼問題,也可能會聯想起現實環境中出現的亂碼問題。
問題彷佛有了思路,ok,讓咱們繼續把實驗作下去:
[php]
--session 1 (或者session 2,在這裏無所謂)
SQL> select col1,dump(col2,1016) from test;
COL1
----------
DUMP(COL2,1016)
--------------------------------------------------------------------------------
2
Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
1
Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd
[/php]
咱們使用了dump函數,結果看起來很明顯了,兩個徹底相同的字符,在不一樣的字符集環境下,在數據庫中存儲成了不一樣的編碼。
對於ZHS16GBK的字符集客戶端輸入的字符"中國",AL32UTF8使用了3個字節來分別存儲一個字符,即:
中--e4,b8,ad
國--e5,9b,bd
咱們也能夠分別對這個字符進行驗證:
[php]
--session 1
SQL> select dump('中',1016) from dual;
DUMP('中',16)
--------------------------------------------
Typ=96 Len=3 CharacterSet=AL32UTF8: e4,b8,ad --字符「中」 ,和上面直接從數據庫中讀取存儲的字符編碼一致。
SQL> select dump('國',1016) from dual;
DUMP('國',16)
--------------------------------------------
Typ=96 Len=3CharacterSet=AL32UTF8: e5,9b,bd --字符「國」 ,和上面直接從數據庫中讀取存儲的字符編碼一致。
[/php]
若是使用session 2直接對着兩個字符進行測試,同樣會獲得相同的結果(筆者已經作過測試,這裏爲了不冗長,刪掉了).
讓咱們從新來理一下思路,並提出幾個問題:
1:爲何顯示爲相同的字符,存儲到數據庫中卻變成了不一樣的編碼?
2:咱們在向數據庫中插入數據的時候,oracle究竟作了些什麼?
3:操做系統字符集,客戶端字符集,數據庫字符集到底是什麼關係?
帶着這些疑惑,讓咱們接着作實驗,全部的疑團和猜想都會在試驗中得以驗證。
個人思路是,先取得測試環境的相關參數。
1:windows字符集(codepage)
咱們使用chcp命令來得到windows使用的字符集
[php]
c:\chcp
活動的代碼頁: 936
[/php]
經過oracle的官方文檔閱讀,咱們能夠將它等同於ZHS16GBK字符集(在安裝oracle時,oracle會找到安裝平臺的字符集,並默認將對應的字符集設置成與它相同,在這裏,數據庫默認的字符集自己應該是ZHS16GBK,但我強制將它修改成AL32UTF8)。
因此如今咱們能夠認爲,咱們使用的操做系統是ZHS16GBk字符集,那麼咱們在這個環境下輸入的字符(也能夠說是顯示的字符,用的就是這個字符集的編碼)。
讓咱們繼續討論問題。
咱們如今要討論一下客戶端字符集到底是用來作什麼的。
咱們知道,不少字符集都有本身的編碼方式,換句話說,相同的字符,在不一樣的字符集裏對應的編碼多是不同的。
客戶端的字符集就是爲了讓數據庫知道咱們傳遞過去的字符是屬於那種字符集,以便於oracle在存儲字符時作相應的編碼映射。
拿上面的例子來講:
好比字符"中國"
在ZHS16GBK字符集中,它的編碼是:d6,d0,b9,fa
在AL32UTF8字符集中,它的編碼是:e4,b8,ad,e5,9b,bd
讓咱們看看例子中兩個session輸入的相同字符在數據庫中存儲對應的編碼:
[php]
SQL> select col1,dump(col2,1016) from t1;
COL1
----------
DUMP(COL2,1016)
--------------------------------------------------------------------------------
2
Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
1
Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd
[/php]
對於session 1,咱們設置的客戶端字符集爲zhs16gbk。
當咱們和數據庫創建session後,數據庫將認爲這個客戶端以zhs16gbk字符集編碼的方式向數據庫發送字符,由於數據庫的字符集是al32utf8,因此字符要以這個字符集的編碼來存儲,此時oracle就會作一個字符編碼轉換,也就是將字符集zhs16gbk中編碼爲d6,d0,b9,fa 的字符編碼映射成字符集爲al32utf8編碼爲e4,b8,ad,e5,9b,bd,在字符集al32utf8的編碼裏,e4,b8,ad,e5,9b,bd對應的字符爲"中國".
對於session 2,咱們設置的客戶端字符集爲al32utf8。
當咱們和數據庫創建session後,數據庫看到客戶端的字符集和數據庫的字符集一致,此時oracle將不會再對字符做轉換,由於它認爲兩邊的字符編碼是一致的。而此時,咱們欺騙了數據庫,儘管咱們將客戶端字符集設置爲和數據庫一致,可是其實咱們使用的是zhs16gbk字符集編碼(由於此時windows使用的就是這個字符編碼),對於字符"中國",zhs16gbk字符集裏對應的編碼爲d6,d0,b9,fa。此時,oracle不加理會的直接將這個編碼保存到了數據庫中。當咱們分別將這兩個字符dump出來的時候,就獲得下面這樣的結果。
[php]
SQL> select col1,dump(col2,1016) from test;
COL1
----------
DUMP(COL2,1016)
--------------------------------------------------------------------------------
2
Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
1
Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd
[/php]
下面咱們就進入到了咱們最關心的地方,亂碼,讓咱們繼續咱們的試驗。
[php]
--session 1
SQL>
SQL> insert into t1 values('中國',1);
1 row created.
SQL> commit;
Commit complete.
SQL> select * from t1;
COL COL2
------------ ----------
中國 1
??? 2
--session 2
SQL> insert into t1 values('中國',2);
1 row created.
SQL> commit;
Commit complete.
SQL> select * from t1;
COL COL2
------ ----------
涓浗 1
中國 2
[/php]
session 1,咱們看到session 2輸入的字符"中國"變成了亂碼"???",
session 2,咱們看到session 1輸入的字符"中國"變成了另外的字符"涓浗",
下面咱們來分析一下這中間數據庫,客戶端和操做系統都發生了那些事情。
上面已經討論了:
session 1 輸入的字符"中國" 在數據庫中存儲的字符編碼爲」e4,b8,ad,e5,9b,bd".
session 2 輸入的字符"中國" 在數據庫中存儲的字符編碼爲」d6,d0,b9,fa".
當session 1開始查詢時,oracle從表中取出這兩個字符,並按照字符集al32utf8和字符集zhs16gbk的編碼映射表,將它的轉換成zhs16gbk字符編碼,對於編碼「e4,b8,ad,e5,9b,bd」,它對應的zhs16gbk的字符編碼爲"d6,d0,b9,fa",這個編碼對應的字符爲」中國「,因此咱們看到了這個字符正常顯示出來了,而對於字符集al32utf8字符編碼「d6,d0,b9,fa」,因爲咱們用於顯示字符的windows環境使用的是zhs16gbk字符集,而在zhs16gbk字符集裏面並無對應這個編碼的字符或者屬於沒法顯示的符號,因而使用了"?"這樣的字符來替換,這就是爲何咱們看到session 2輸入的字符變成了這樣的亂碼。
當session 2開始查詢時,oracle從表中取出這兩個字符,因爲客戶端(nls_lang)和數據庫的字符集設置一致,oracle將忽略字符的轉換問題,因而直接將數據庫中存儲的字符返回給客戶端。對於編碼爲"d6,d0,b9,fa"的字符,返回給客戶端,而客戶端顯示所用的字符集正好是zhs16gbk,在這個字符集裏,這個編碼對應的是"中國"兩個字符,因此就正常顯示出來了。對於字符編碼「e4,b8,ad,e5,9b,bd」,返回到客戶端後,由於在zhs16gbk裏採用的是雙字節存儲字符方式,因此這6字節對應了zhs16gbk字符集的3個字符,也就是咱們看到的"涓浗".
到如今爲止,我想咱們基本上搞清楚了爲何平常查詢時會遇到亂碼的問題。
其實亂碼,說到底就是用於顯示字符的操做系統沒有在字符編碼中找到對應的字符致使的,形成這種現象的主要緣由就是:
1:輸入操做的os字符編碼和查詢的os字符編碼不一致致使出現亂碼。
2:輸入操做的客戶端字符集(nls_lang)和查詢客戶端字符集(nls_lang)不一樣,也可能致使查詢返回亂碼或者錯誤的字符。
還有一個問題須要解釋一下:
在上面的例子中,相同的字符在不一樣的字符集中對應着不一樣的字符編碼,這個一般稱爲字符集不兼容或者不徹底兼容,好比zhs16gbk和al32utf8,他們存儲的ascii碼的字符編碼都是相同的,但對於漢字倒是不一樣的。
若是兩個字符集對於相同的字符采用的相同的字符編碼,咱們稱之爲字符兼容,範圍大的叫作範圍小的字符集的超級。咱們一般遇到的zhs16cgb231280,zhs16gbk就是這樣的狀況,後者是前者的超級。
在實際的環境中除了字符顯示以外,還有其餘的地方會涉及到字符集問題。好比:
1:exp/imp
2:sql*lorder
3:應用程序的字符輸入
......
一個誤區:
看到不少人在出現亂碼的時候都首先要作的就是將客戶端字符集設置和數據庫一致,其實這是沒有太多根據的。
設想一下,假如數據庫字符集是al32utf8,裏面存儲這一些中文字符,而個人客戶端操做系統是英文的,此時我將客戶端的nls_lang設置成al32utf8,這樣會解決問題嗎?這樣客戶端就能顯示中文了嗎?客戶端就能輸入中文了嗎?如今客戶端是英文的,它的字符集里根本就沒有漢字的編碼,咱們簡單的修改一下客戶端的字符集又有什麼用?前面已經討論了,這個設置無非就是告訴oracle我將以什麼樣的字符集與數據庫進行數據交換,對於解決亂碼問題毫無關係。
正確的作法是將客戶端的操做系統改爲支持中文字符,並將客戶端字符集改爲和操做系統一致的字符集,這樣才能真正的解決問題。
oracle視頻教程請關注:http://u.youku.com/user_video/id_UMzAzMjkxMjE2.html