在Oracle資料庫中如果伺服器端用戶端字元集設定不當,就會造成儲存到資料庫的資料不會正常儲存,或者資料庫裡的資料在用戶端不能正常顯示,出現爛碼的現象。
尤其是以SQLPLUS操作的時候,由於SQLPLUS的"bug"(或者叫使用者使用不當),會造成一些看起來不’正常‘的爛碼現象,下面來解釋一下:
1,查看資料庫的字元集:
SQL> select * from database_properties where property_name='NLS_CHARACTERSET
PROPERTY_NAME PROPERTY_VALUE DESCRIPTION
------------------------------ ------------------------------ --------------
NLS_CHARACTERSET UTF8 Character set
2,用戶端的作業系統環境為中文,在預設的設定下通過sqlplus插入一條資料後提交:
SQL> insert into yorker.test values('中文','SQLPLUS default');
這時候在插入的時候,用戶端的字元,比如’中文 ‘是以作業系統的編碼方式(比如ZHS16GBK)編碼後發送到Oracle伺服器端,Oracle在存入資料庫的時候轉換成
UTF8儲存。
3,以預設的方式開啟SQLPLUS查詢
SQL> select value,nlstype,dump(value) dv from yorker.test;
VALUE NLSTYPE DV
-------------------- -------------------- ------------------------------------
中文 SQLPLUS default Typ=1 Len=6: 228,184,173,230,150,135
顯示正常,Oracle伺服器讀取欄位的二進位內容,根據用戶端的編碼,轉換成’中文‘在用戶端編碼對應的編碼內容發送到用戶端。
dump(value)就是伺服器端’中文‘的16進位編碼的內容,utf8的內容形式。
4在用戶端設定 nls_lang後進入SQLPLUS
C:\>set nls_lang=american_america.utf8
C:\>sqlplus sys/sys as sysdba
SQL> select value,nlstype,dump(value) dv from yorker.test;
VALUE NLSTYPE DV
------------------------------ ------------------------------ --------------------------------------
涓枃 SQLPLUS default Typ=1 Len=6: 228,184,173,230,150,135
查詢到的結果如上出現爛碼,原因是SQLPLUS在查詢的時候,告訴了Oracle伺服器用戶端的編碼為UTF8,這時候Oracle不做轉換把以UTF8編碼的內容’中文‘發送到
SQLPLUS,而SQLPLUS卻以作業系統的編碼(比如ZHS16GBK)去識別這個內容,結果就是不能識別的爛碼。set nls_lang=american_america.utf8後不以utf8編碼的形式來識別字元是爛碼的原因,Oracle儲存的內容實施正確的,是utf8的正確形式,在別的用戶端仍然可以正確顯示(如預設的SQLPLUS,JAVA應用程式等)
5,在用戶端設定為utf8的sqlplus插入資料
C:\>set nls_lang=american_america.utf8
C:\>sqlplus sys/sys as sysdba
SQL> insert into yorker.test values('中文','SQLPLUS UTF8');
這時候發生了一個“錯誤”,’中文‘是以作業系統的ZHS16GBK的編碼格式發送到Oracle伺服器的,同時sqlplus又告訴了伺服器發送的編碼是utf8,所以Oracle伺服器不做轉換把ZHS16GBK編碼格式的’中文‘儲存到了伺服器的磁碟上,但是Oracle認為這個編碼是utf8,下次如果別的用戶端,不是utf8的用戶端在查詢的時候,Oracle做轉換,這時候會轉換不正確,因為ZHS16GBK編碼格式的’中文‘在utf8裡可能找不到對應的字元或者是不同的字元。
5,在用戶端設定為utf8的sqlplus查詢資料
C:\>set nls_lang=american_america.utf8
C:\>sqlplus sys/sys as sysdba
SQL> select value,nlstype,dump(value) dv from yorker.test;
VALUE NLSTYPE DV
------------------------------ ------------------------------ ---------------------------------------
中文 SQLPLUS UTF8 Typ=1 Len=4: 214,208,206,196
剛才以utf8的編碼顯示正確,這是因為“錯上加錯”就對了:sqlplus告訴Oracle用戶端是utf8,Oracle不做轉換直接把資料庫儲存的二進位內容發到了用戶端,由於和儲存的時候的內容是一致的,所以仍然能夠正確顯示。
這時候如果用戶端設定的nls_lang不是和伺服器的utf8一樣,伺服器在發送到用戶端之前做了一個’轉換‘,顯示就不對了,如下例:
?? SQLPLUS UTF8 Typ=1 Len=4: 214,208,206,196
在一個JAVA的用戶端應用程式中
private static void testEncoding() throws SQLException {
Connection conn = NONXADBUtil.getConnection("ORCL");
PreparedStatement sta = conn.prepareStatement("select value,nlstype,dump(value) dv from yorker.test");
ResultSet rset = sta.executeQuery();
while (rset.next()) {
System.out.println(rset.getString(1) + " " + rset.getString(2) + " "+ rset.getString(3) );
}
rset.close();
sta.close();
conn.close();
}
顯示的內容如下:
中文 SQLPLUS default Typ=1 Len=6: 228,184,173,230,150,135
�� SQLPLUS UTF8 Typ=1 Len=4: 214,208,206,196
發生爛碼的原因是,Oracle將’中文‘(214,208,206,196)以utf8的編碼格式識別,識別出錯,轉換成JAVA客服端想要的字元這一步更是’錯爛‘的完成的。
*在匯入匯出的時候遵循的原則,
匯出時:用戶端設定nls_lang和資料來源的資料庫一致。
匯入時:用戶端設定為匯出時設定的編碼,如果匯出是由別人完成,一定要知道匯出時候設定的編碼。匯入時候要設定成和匯出時候一致。
匯出的檔案第2,第3個位元組記錄的是該檔案的編碼,用ultraedir可以查看,是16進位。比方說為03 54,先轉成10進位得到
select to_number('0354','xxxx') from dual 得到 852,在可以通過 select nls_charset_name(852) from dual 知道是ZHS16GBK