最后我們來討論一下亂碼的產(chǎn)生。
通常在我們的現(xiàn)實(shí)環(huán)境中,存在3個(gè)字符集設(shè)置。
第一: 客戶端應(yīng)用字符集(Client Application Character Set)
第二: 客戶端NLS_LANG參數(shù)設(shè)置
第三: 服務(wù)器端,數(shù)據(jù)庫字符集(Character Set)設(shè)置
我們說,一個(gè)字符在客戶端應(yīng)用(比如SQLPLUS,CMD,NOTEPAD等)中以怎樣的字符顯示取決于客戶端操作系統(tǒng),客戶端能夠顯示怎樣的字符,
我們就可以在應(yīng)用中錄入這些字符,至于這些字符能否在數(shù)據(jù)庫中正常存儲,就和另外的兩個(gè)字符集設(shè)置緊密相關(guān)了。
在傳輸過程中,客戶端NLS_LANG主要用于進(jìn)行轉(zhuǎn)換判斷
如果NLS_LANG等于數(shù)據(jù)庫字符集,則不進(jìn)行任何轉(zhuǎn)換直接把字符插入數(shù)據(jù)庫
如果不同則進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換主要有兩個(gè)任務(wù)
- 如果存在對應(yīng)關(guān)系,則把相應(yīng)二進(jìn)制編碼經(jīng)過映射后(這一步映射以后,所代表的字符可能發(fā)生轉(zhuǎn)換)傳遞給數(shù)據(jù)庫
- 如果不存在對應(yīng)關(guān)系,則傳遞一個(gè)替換字符(很多平臺就是?)
數(shù)據(jù)庫字符集,在和客戶端NLS_LANG不同時(shí),會把經(jīng)過NLS_LANG轉(zhuǎn)換的字符進(jìn)行進(jìn)一步處理
- 對于?(即不存在對應(yīng)關(guān)系的字符)直接以?形式存放入數(shù)據(jù)庫
- 對于其他字符,在NLS_LANG和數(shù)據(jù)庫字符集之間進(jìn)行轉(zhuǎn)換后存入。
以下我們來看一下最為常見的字符集及亂碼的產(chǎn)生:
1.當(dāng)NLS_LANG字符集與數(shù)據(jù)庫字符集不同,同時(shí)NLS_LANG不同于Server端字符集設(shè)置
在這種情況下,存在兩種可能:
-
客戶端輸入的字符在NLS_LANG中沒有對應(yīng)的字符,這時(shí)無法轉(zhuǎn)換,NLS_LANG使用替換字符替代這些無法映射的字符(這一步轉(zhuǎn)換在TTS中
完成),在很多字符集中這個(gè)替代字符就是”?” -
當(dāng)客戶端的字符在NLS_LANG中對應(yīng)了不同的字符時(shí),傳遞給數(shù)據(jù)庫以后發(fā)生轉(zhuǎn)換,存儲的是字符,但是已經(jīng)丟失了元數(shù)據(jù),數(shù)據(jù)庫中
的字符不再代表客戶端的輸入。而且這個(gè)過程不可逆,這也就是為什么很多時(shí)候在客戶端輸入的是正常的編碼,查詢之后會得到未知字符的原因。
我們通過上圖來簡單說明一下這個(gè)過程,當(dāng)客戶端在WE8ISO8859P15字符集時(shí),輸入歐元符號: €,這時(shí)客戶端NLS_LANG和數(shù)據(jù)庫端字符集不同,
進(jìn)行第一次轉(zhuǎn)換,客戶端€符號編碼是A4,在NLS_LANG轉(zhuǎn)換時(shí),A4對應(yīng)了NLS_LANG中的‘¤’,這一步的轉(zhuǎn)換產(chǎn)生了錯(cuò)誤映射。由于數(shù)據(jù)庫字符集不
同于NLS_LANG設(shè)置,這時(shí)進(jìn)一步的轉(zhuǎn)換發(fā)生了,存入數(shù)據(jù)庫的編碼變成了C2A4,雖然同NLS_LANG進(jìn)行了正確的轉(zhuǎn)換,但是客戶端錄入的數(shù)據(jù)已經(jīng)
損壞或者丟失了。
我們可以用我們熟悉的字符集做一個(gè)簡單的測試:
測試環(huán)境:
客戶端應(yīng)用為中文18030字符集
NLS_LANG設(shè)置為US7ASCII字符集
數(shù)據(jù)庫CHARACTER SET為ZHS16GBK
c:\>set NLS_LANG=AMERICAN_AMERICA.US7ASCII
c:\>sqlplus eygle/eygle
SQL*Plus: Release 9.2.0.4.0 - Production on Tue Nov 4 01:19:57 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> select name,dump(name) from test;
NAME DUMP(NAME)
--------------------------------------------------
2bJT Typ=1 Len=4:
50,98,74,84
這時(shí)候我們發(fā)現(xiàn),查詢出來的是混亂的字符,我們把這些字符轉(zhuǎn)換為2進(jìn)制就是
110010 1100010 1001010 1010100
補(bǔ)全8位就是 00110010 01100010 01001010 01010100
我們把首位換成1 10110010 11100010 11001010 11010100
我們來看正確的存儲:
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:
SQL> insert into test values('測試'); 1 row created.
SQL> col dump(name) for a30
NAME DUMP(NAME)
1 row selected.
我們把這個(gè)結(jié)果轉(zhuǎn)換為2進(jìn)制表示
這個(gè)結(jié)果正是我們前面亂碼首位補(bǔ)全1后的結(jié)果。
這個(gè)測試說明在US7ASCII轉(zhuǎn)換中文的時(shí)候除去了首位的 1,這樣就丟失了元數(shù)據(jù),導(dǎo)致亂碼出現(xiàn),NLS_LANG的轉(zhuǎn)換作用由此可加一斑!
|
3. NLS_LANG和數(shù)據(jù)庫字符集相同時(shí)
在這種情況下,數(shù)據(jù)庫端對客戶端傳遞過來的編碼不進(jìn)行任何轉(zhuǎn)換(這樣可以提高性能),直接存儲進(jìn)入數(shù)據(jù)庫,那么這時(shí)候就存在和上面同樣的問題,
如果客戶端傳遞過來的字符集在數(shù)據(jù)庫中有正確的對應(yīng)就可以正確存儲,如果沒有,就會被替換字符置換成?,亂碼就這樣產(chǎn)生了。
如上圖所示,當(dāng)NLS_LANG和數(shù)據(jù)庫字符集設(shè)置相同都為UTF8時(shí),客戶端的歐元符號的編碼A4就不會經(jīng)過任何轉(zhuǎn)換就插入到數(shù)據(jù)庫中,而在UTF8的數(shù)
據(jù)庫中,A4代表的是一個(gè)非法字符。
我們來看一個(gè)簡單的測試
測試環(huán)境:
客戶端字符集應(yīng)用為中文GB18030
客戶端NLS_LANG為US7ASCII
數(shù)據(jù)庫字符集為US7ASCII
我們知道這個(gè)時(shí)候,存入的數(shù)據(jù),數(shù)據(jù)庫不進(jìn)行任何轉(zhuǎn)換,在以下的測試中,我們看到中文在US7ASCII字符集下得以正確顯示。
|
c:\>set nls_lang=AMERICAN_AMERICA.US7ASCII
c:\>sqlplus eygle/eygle
SQL*Plus: Release 9.2.0.4.0 - Production on Tue Nov 4 01:02:04 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> commit;
Commit complete.
SQL> select * from test;
NAME
----------
測試
1 row selected.
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.
SQL> select * from nls_database_parameters;
PARAMETER VALUE
------------------------------ ----------------------------------------
NLS_LANGUAGE AMERICAN
NLS_TERRITORY AMERICA
NLS_CURRENCY $
NLS_ISO_CURRENCY AMERICA
NLS_NUMERIC_CHARACTERS .,
NLS_CHARACTERSET US7ASCII
NLS_CALENDAR GREGORIAN
NLS_DATE_FORMAT DD-MON-RR
NLS_DATE_LANGUAGE AMERICAN
NLS_SORT BINARY
NLS_TIME_FORMAT HH.MI.SSXFF AM
PARAMETER VALUE
------------------------------ ----------------------------------------
NLS_TIMESTAMP_FORMAT DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT HH.MI.SSXFF AM TZR
NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZR
NLS_DUAL_CURRENCY $
NLS_COMP BINARY
NLS_LENGTH_SEMANTICS BYTE
NLS_NCHAR_CONV_EXCP FALSE
NLS_NCHAR_CHARACTERSET AL16UTF16
NLS_RDBMS_VERSION 9.2.0.4.0
20 rows selected.
SQL>
|
結(jié)語:
對于DBA來說,有一個(gè)很重要的原則就是:不要把你的數(shù)據(jù)庫置于危險(xiǎn)的境地!
這就要求我們,在進(jìn)行任何可能對數(shù)據(jù)庫結(jié)構(gòu)發(fā)生改變的操作之前,先做有效的備份,很多DBA沒有備份的操作中得到了慘痛的教訓(xùn)。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

