原文鏈接:
http://www.eygle.com/special/nls_character_set_06.htm
原文發表于itpub技術叢書《oracle數據庫dba專題技術精粹》,未經許可,嚴禁轉載本文.
最后我們來討論一下亂碼的產生。
通常在我們的現實環境中,存在3個字符集設置。
第一: 客戶端應用字符集(client application character set)
第二: 客戶端nls_lang參數設置
第三: 服務器端,數據庫字符集(character set)設置
我們說,一個字符在客戶端應用(比如sqlplus,cmd,notepad等)中以怎樣的字符顯示取決于客戶端操作系統,客戶端能夠顯示怎樣的字符,
我們就可以在應用中錄入這些字符,至于這些字符能否在數據庫中正常存儲,就和另外的兩個字符集設置緊密相關了。
在傳輸過程中,客戶端nls_lang主要用于進行轉換判斷
如果nls_lang等于數據庫字符集,則不進行任何轉換直接把字符插入數據庫
如果不同則進行轉換,轉換主要有兩個任務
如果存在對應關系,則把相應二進制編碼經過映射后(這一步映射以后,所代表的字符可能發生轉換)傳遞給數據庫 如果不存在對應關系,則傳遞一個替換字符(很多平臺就是?)
數據庫字符集,在和客戶端nls_lang不同時,會把經過nls_lang轉換的字符進行進一步處理
對于?(即不存在對應關系的字符)直接以?形式存放入數據庫 對于其他字符,在nls_lang和數據庫字符集之間進行轉換后存入。
以下我們來看一下最為常見的字符集及亂碼的產生:
1.當nls_lang字符集與數據庫字符集不同,同時nls_lang不同于server端字符集設置
在這種情況下,存在兩種可能:
客戶端輸入的字符在nls_lang中沒有對應的字符,這時無法轉換,nls_lang使用替換字符替代這些無法映射的字符(這一步轉換在tts中
完成),在很多字符集中這個替代字符就是”?” 當客戶端的字符在nls_lang中對應了不同的字符時,傳遞給數據庫以后發生轉換,存儲的是字符,但是已經丟失了元數據,數據庫中
的字符不再代表客戶端的輸入。而且這個過程不可逆,這也就是為什么很多時候在客戶端輸入的是正常的編碼,查詢之后會得到未知字符的原因。
我們通過上圖來簡單說明一下這個過程,當客戶端在we8iso8859p15字符集時,輸入歐元符號: €,這時客戶端nls_lang和數據庫端字符集不同,
進行第一次轉換,客戶端€符號編碼是a4,在nls_lang轉換時,a4對應了nls_lang中的‘¤’,這一步的轉換產生了錯誤映射。由于數據庫字符集不
同于nls_lang設置,這時進一步的轉換發生了,存入數據庫的編碼變成了c2a4,雖然同nls_lang進行了正確的轉換,但是客戶端錄入的數據已經
損壞或者丟失了。
我們可以用我們熟悉的字符集做一個簡單的測試:
測試環境:
客戶端應用為中文18030字符集
nls_lang設置為us7ascii字符集
數據庫character set為zhs16gbk
c:/>set nls_lang=american_america.us7asciic:/>sqlplus eygle/eyglesql*plus: release 9.2.0.4.0 - production on tue nov 4 01:19:57 2003copyright (c) 1982, 2002, oracle corporation. all rights reserved.connected to:oracle9i enterprise edition release 9.2.0.4.0 - productionwith the partitioning, oracle label security, olap and oracle data mining optionsjserver release 9.2.0.4.0 - productionsql> insert into test values('測試');1 row created.sql> select name,dump(name) from test;namedump(name)--------------------------------------------------2bjttyp=1 len=4: 50,98,74,84這時候我們發現,查詢出來的是混亂的字符,我們把這些字符轉換為2進制就是110010 1100010 1001010 1010100補全8位就是 00110010 01100010 01001010 01010100我們把首位換成1 10110010 11100010 11001010 11010100我們來看正確的存儲:
c:/>set nls_lang=american_america.zhs16gbk
c:/>sqlplus eygle/eygle
sql*plus: release 9.2.0.4.0 - production on tue nov 4 01:40:18 2003
copyright (c) 1982, 2002, oracle corporation. all rights reserved.
connected to:
oracle9i enterprise edition release 9.2.0.4.0 - production
with the partitioning, oracle label security, olap and oracle data mining options
jserver release 9.2.0.4.0 - production
sql> insert into test values('測試');
1 row created.
sql> col dump(name) for a30
sql> select name,dump(name) from test;
name dump(name)
---------- ------------------------------
測試 typ=1 len=4: 178,226,202,212
1 row selected.
我們把這個結果轉換為2進制表示
10110010 11100010 11001010 11010100
這個結果正是我們前面亂碼首位補全1后的結果。
這個測試說明在us7ascii轉換中文的時候除去了首位的 1,這樣就丟失了元數據,導致亂碼出現,nls_lang的轉換作用由此可加一斑!
3. nls_lang和數據庫字符集相同時
在這種情況下,數據庫端對客戶端傳遞過來的編碼不進行任何轉換(這樣可以提高性能),直接存儲進入數據庫,那么這時候就存在和上面同樣的問題,
如果客戶端傳遞過來的字符集在數據庫中有正確的對應就可以正確存儲,如果沒有,就會被替換字符置換成?,亂碼就這樣產生了。
如上圖所示,當nls_lang和數據庫字符集設置相同都為utf8時,客戶端的歐元符號的編碼a4就不會經過任何轉換就插入到數據庫中,而在utf8的數
據庫中,a4代表的是一個非法字符。
我們來看一個簡單的測試
測試環境:
客戶端字符集應用為中文gb18030
客戶端nls_lang為us7ascii
數據庫字符集為us7ascii
我們知道這個時候,存入的數據,數據庫不進行任何轉換,在以下的測試中,我們看到中文在us7ascii字符集下得以正確顯示。
c:/>set nls_lang=american_america.us7asciic:/>sqlplus eygle/eyglesql*plus: release 9.2.0.4.0 - production on tue nov 4 01:02:04 2003copyright (c) 1982, 2002, oracle corporation. all rights reserved.connected to:oracle9i enterprise edition release 9.2.0.4.0 - productionwith the partitioning, oracle label security, olap and oracle data mining optionsjserver release 9.2.0.4.0 - productionsql> insert into test values('測試');1 row created.sql> commit;commit complete.sql> select * from test;name----------測試1 row selected.sql> col dump(name) for a30sql> select name,dump(name) from test;name dump(name)---------- ------------------------------測試 typ=1 len=4: 178,226,202,2121 row selected.sql> select * from nls_database_parameters;parameter value------------------------------ ----------------------------------------nls_language americannls_territory americanls_currency $nls_iso_currency americanls_numeric_characters .,nls_characterset us7asciinls_calendar gregoriannls_date_format dd-mon-rrnls_date_language americannls_sort binarynls_time_format hh.mi.ssxff amparameter value------------------------------ ----------------------------------------nls_timestamp_format dd-mon-rr hh.mi.ssxff amnls_time_tz_format hh.mi.ssxff am tzrnls_timestamp_tz_format dd-mon-rr hh.mi.ssxff am tzrnls_dual_currency $nls_comp binarynls_length_semantics bytenls_nchar_conv_excp falsenls_nchar_characterset al16utf16nls_rdbms_version 9.2.0.4.020 rows selected.sql>
結語:
對于dba來說,有一個很重要的原則就是:不要把你的數據庫置于危險的境地!
這就要求我們,在進行任何可能對數據庫結構發生改變的操作之前,先做有效的備份,很多dba沒有備份的操作中得到了慘痛的教訓。