[轉載]Oracle資料庫字符集問題解析3

lastwinner發表於2005-11-12

繼續問題解答

[@more@]

ygzeng:
收穫很大,在測試過程中,增加了一些新的case,在測試過程中,發現“替代字元”往往容易轉換成“靠”,“?”,“?”,“驢”,對這些亂碼的產生原因有些不解!

1,資料庫字符集設為gb2312,客戶端設為WE8ISO8859P1,然後插入資料,讀取資料
SET NLS_LANG=AMERICAN_AMERICA.WE8ISO8859P1
sqlplus

select * from nls_database_parameters where PARAMETER='NLS_CHARACTERSET';
PARAMETER VALUE
--------------------------------- ---------------------------------
NLS_CHARACTERSET ZHS16CGB231280

insert into charset_test(server_charset,os_charset,client_charset,cn_char) values('GB2312','GB2312','ISO8859','中文');
commit;
column dump format a40
select server_charset,os_charset,client_charset,cn_char,dump(cn_char) dump from charset_test where client_charset='ISO8859' and os_charset='GB2312';
SERVER_ OS_CHAR CLIENT_ CN_CHAR DUMP
------- ------- ------- ------- ------------------------------
GB2312 GB2312 ISO8859 靠 Typ=1 Len=4: 214,208,206,196
可以看到儲存是完全正確的,但是在讀取的時候,出現“靠”(在gb2312中編碼為191,191),我猜測,在這種環境下“替代字元”的編碼是BF,由於兩個替代字元BFBF,導致出現了一個“靠”
疑問:在Oracle中,各種環境下的替代字元是不是都是"?"?按照測試結果好像不是。另外,為什麼顯示結果“靠”(191,191)
在ISO8859 字元環境下,“替代字元”也應該是"?",在gb2312中,倒是有可能是中文“?”其編碼是:163,191,但是無論如何,都不應該是”靠“
在伺服器端字符集為iso8859,客戶端字符集為:gb2312的環境下進行讀取,儲存測試!

SET NLS_LANG=AMERICAN_AMERICA.ZHS16CGB231280
sqlplus

select * from nls_database_parameters where PARAMETER='NLS_CHARACTERSET';
PARAMETER VALUE
--------------------------------- ---------------------------------
NLS_CHARACTERSET WE8ISO8859P1
insert into charset_test(server_charset,os_charset,client_charset,cn_char) values('ISO8859','GB2312','GB2312','中文');
commit;
column dump format a40
select server_charset,os_charset,client_charset,cn_char,dump(cn_char) dump from charset_test where client_charset='GB2312' and os_charset='GB2312';

SERVER_CHARSET OS_CHARSET CLIENT_CHARSET CN_CHAR
-------------- -------------- -------------- --------------
DUMP
------------------------------
ISO8859 GB2312 GB2312 ??
Typ=1 Len=2: 191,191
疑問:為什麼儲存了191?難道191真的是某種字符集的“替代字元”?191但是顯示結果為什麼是“?”?

SET NLS_LANG=AMERICAN_AMERICA.UTF8
sqlplus

insert into charset_test(server_charset,os_charset,client_charset,cn_char) values('ISO8859','GB2312','UTF8','中文');
commit;
column dump format a40
select server_charset,os_charset,client_charset,cn_char,dump(cn_char) dump from charset_test where client_charset='UTF8' and os_charset='GB2312';

SERVER_CHARSET OS_CHARSET CLIENT_CHARSET
--------------------- --------------------- ---------------------
CN_CHAR DUMP
--------------------- ------------------------------
ISO8859 GB2312 UTF8
驢驢 Typ=1 Len=2: 191,191
疑問:為什麼191在UTF8下表示成為“驢”?

jeffli73:
感謝ygzeng朋友補充的例子,下面我就分析一下
請ygzeng朋友給出實驗時資料庫的版本、client端OS的型別、版本,因為這些因素有可能導致實驗結果的差別。

1.資料庫字符集設為gb2312,客戶端設為WE8ISO8859P1。
insert into charset_test(server_charset,os_charset,client_charset,cn_char) values('GB2312','GB2312','ISO8859','中文');
我的實驗環境是資料庫與client端在一臺PC機上,OS為windows20003,Oracle為10.1.0.2,字符集請選用GBK。
執行上述插入與你的結果不一樣,請將你的實驗環境再詳細描述一下。
關於插入後查詢結果:
select server_charset,os_charset,client_charset,cn_char,dump(cn_char) dump from charset_test where
client_charset='ISO8859' and os_charset='GB2312';
SERVER_ OS_CHAR CLIENT_ CN_CHAR DUMP
------- ------- ------- ------- ------------------------------
GB2312 GB2312 ISO8859 靠 Typ=1 Len=4: 214,208,206,196
是可以理解的,伺服器端為gb2312,(214,208)與(206,196)在ISO8859-1中沒有對應字元,需要轉換為替換字元,從實驗結果看轉換為191(0xBF),而在ISO8859-1中191(0xBF)對應的字元為¿,好象一個倒寫的?,而191-128(即去掉191二進位制最高位的1)為63,我們可以查一下ASCII碼,正好是問號(?)的編碼,看來拿此字元作為WE8ISO8859P1的替換字元也沒什麼可奇怪的。
Oracle將(214,208,206,196)轉換為(191,191),而client作業系統如你所說也採用GB2312,而(191,191)正是“靠”的編碼,所以顯示“靠”。

ygzeng:
jeffli73,我的資料庫版本是8.1.7.0.0,客戶端安裝了英文版本的windows2000+sp4(地區設定為:中華人民共和國,預設語言是:簡體中文),客戶端的資料庫版本是:9.2.0.5!
是不是在不同的字符集下面,"替代字元"都不一樣,並且"替代字元"由伺服器端的字符集決定?而且這些"替代字元"在不同的環境下可以相互替代,也就是說iso8859下的" ¿ "(BF),如果客戶端是gb2312,就會顯示"?"(A3BF)


jeffli73:
繼續解釋ygzeng朋友第二個實驗結果
2.在伺服器端字符集為iso8859,客戶端字符集為:gb2312的環境下進行讀取,儲存測試!
insert into charset_test(server_charset,os_charset,client_charset,cn_char) values('ISO8859','GB2312','GB2312','中文');
select server_charset,os_charset,client_charset,cn_char,dump(cn_char) dump from charset_test where client_charset='GB2312' and os_charset='GB2312';

SERVER_CHARSET OS_CHARSET CLIENT_CHARSET CN_CHAR
-------------- -------------- -------------- --------------
DUMP
------------------------------
ISO8859 GB2312 GB2312 ??
Typ=1 Len=2: 191,191
客戶端字符集為:gb2312,漢字'中文'無法轉換到iso8859-1字符集,儲存到資料庫的字元為(191),再次證明了(191)為iso8859-1字符集的替換字元。
當查詢時,(191)在GB2312中沒有對應字元,需要轉換為替換字元,即中文問號“?”(A3 BF)。

解釋ygzeng朋友第三個實驗結果
3.
SET NLS_LANG=AMERICAN_AMERICA.UTF8
client的設定本應與作業系統一致,在某些特殊情況下,為避免字符集轉換,設定成與資料庫字符集一致,而此時卻人為設定成第三種字符集,肯定會造成混亂。
insert into charset_test(server_charset,os_charset,client_charset,cn_char) values('ISO8859','GB2312','UTF8','中文');
select server_charset,os_charset,client_charset,cn_char,dump(cn_char) dump from charset_test where client_charset='UTF8'

and os_charset='GB2312';

SERVER_CHARSET OS_CHARSET CLIENT_CHARSET
--------------------- --------------------- ---------------------
CN_CHAR DUMP
--------------------- ------------------------------
ISO8859 GB2312 UTF8
驢驢 Typ=1 Len=2: 191,191

此時'中文'實際用的GB2312的編碼,而使用者卻告訴Oracle這是UTF8的編碼,轉化為ISO8859-1,沒有對應的字元,轉換為替換字元(191,191)。
而在查詢時,比較有意思,ISO8859-1的字元(191)可以轉化為UTF8的字元,其對應字元為11000010 10111111(C2 BF),而作業系統使用的是GB2312編碼,而(C2 BF)在GB碼中對應的是“驢”字,所以顯示為“驢”。


ygzeng:
jeffli73,關於實驗3,你的解釋是正確的!我後來查了一下,具體過程可能是這樣的:
儲存的時候,由於ISO8859並不支援中文,輸入的“中文”只能用替代字元“¿”(191)來替代!
讀取的時候,根據UCS-2和UTF8的轉換規則2,如果字元位於:0080 - 07FF則取11位,轉換成為:110xxxxx 10xxxxxx,191(BF)位於這個區間,根據這個規則,其轉換成:(C2BF),由於操作體統是GB2312,在GB2312中,驢的編碼是:C2BF
samechsu,資料庫安裝之後,字符集只能從子集向超集轉換,兩個不相容的資料庫的字符集不能轉換的!


jeffli73:
關於實驗3的解釋
對於ygzeng朋友上述解釋有一些不同意見

本來在解釋實驗3時,也想用UCS-2和UTF8的轉換規則,但考慮到本例的源字符集為ISO8859,是一個8位的字符集,而UCS-2是一個16位的字符集,這麼解釋邏輯上並不是很通。所以如下解釋可能更為合理一些:ISO8859中的字元“¿”(191)在UTF8中對應的字元編碼為11000010 10111111(C2 BF)。


ygzeng:
Jeffli73,
Oracle中並不存在UCS(Unicode Character Set)這樣的字符集,Unicode是以UTF8(UCS Transformation Format,變長字符集)或者UTF16(定長字符集)的形式儲存的,你所說的“ISO8859中的字元“¿”(191)在UTF8中對應的字元編碼為11000010 10111111(C2 BF)”,就是一個ucs-2到UTF8的轉換過程,也就是,你的解釋和我的解釋好像沒有什麼區別!

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29867/viewspace-809899/,如需轉載,請註明出處,否則將追究法律責任。

相關文章