基本概念
字符集(Character set):是一個系統支持的所有抽象字符的集合。字符是各種文字和符號的總稱,包括各國家文字、標點符號、圖形符號、數字等。常見的字符集有ASCII,ZHS16GB231280,ZHS16GBK等。
字符編碼(Character Encoding):是一套法則,使用該法則能夠對自然語言的字符的一個集合(如字母表或音節表),與其它的一個集合(如電腦編碼)進行配對。即在符號集合與數字系統之間建立對應關系。與字符集相對應,常見的字符編碼有:ASCii,ZHS16GBK,ZHT16BIG5,ZHS32GB18030等。
字符集的定義其實就是字符的集合,而字符編碼則是指怎麼將這些字符變成字節用於保存、讀取和傳輸。
萬國碼(Unicode):包含了幾乎人類所有可用的字符,每年還在不斷的增加,可以看作是一種通用的字符集。它將全世界所有的字符統一化,統一編碼,不會再出現字符不兼容和字符轉換的問題。
它有以下三種編碼方式:
1.UTF-32編碼:固定使用4個字節來表示一個字符,存在空間利用效率的問題。
2.UTF-16編碼:對相對常用的60000余個字符使用兩個字節進行編碼,其余的使用4字節。
3.UTF- 8編碼:兼容ASCII編碼;拉丁文、希臘文等使用兩個字節;包括漢字在內的其它常用字符使用三個字節;剩下的極少使用的字符使用四個字節。
Oracle字符集基本原理
在搞懂Oracle字符集基本原理之前,一定要先分清以下三個概念:
1. Oracle數據庫服務器字符集:即Oracle以哪種字符編碼存儲字符,可以通過以下語句查出數據庫字符集的設置。
2. 客戶端操作系統字符集:即客戶端操作系統以哪種字符編碼存儲字符。
如果是Windows,可以使用chcp命令獲得代碼頁(code page):
根據該代碼頁,到微軟的官方文檔《National Language Support (NLS) API Reference》找到其對應的字符集。
如果是Linux,字符集在/etc/sysconfig/i18n設置:
3. 客戶端NLS_LANG參數:該參數用於向Oracle指示客戶端操作系統的字符集。
有了以上3個基本概念之後,我來闡述一下Oracle字符集轉換的基本原則:
1.設置客戶端的NLS_LANG為客戶端操作系統的字符集
2.如果數據庫字符集等於NLS_LANG,數據庫和客戶端傳輸字符時不作任何轉換
3.如果它們倆不等,則需要在不同字符集間轉換,只有客戶端操作系統字符集是數據庫字符集子集的基礎上才能正確轉換,否則會出現亂碼。
幾種常見情況分析
下面先看一個例子,再透過現象看本質,我們會針對這個例子進行分析。
該例子如下:
上面例子看起來很詭異,session1和2都能正常顯示自己插入的字符串,又都不能正常顯示對方插入的字符串。為了弄清楚,我們首先得知道數據庫裡對這兩個字符串是怎麼存儲的。我們可以使用dump函數獲得字符在數據庫的編碼:
根據AL32UTF8的編碼,“中國”兩字的正確編碼為(都為3個字節):
中--e4,b8,ad
國--e5,9b,bd
因此session 1插入的字符串在數據庫中的編碼是錯誤的,session 2正確。這也是為什麼一定要設置NLS_LANG為客戶端操作系統的字符集。
但是根據上面實驗我們可以知道,數據庫中存儲正確,並不代表客戶端能正常顯示;同樣地,即時數據庫沒有正確存儲,有時候客戶端也能夠正常顯示,這又是為什麼呢?別急,請聽我慢慢道來:
場景1:session 1插入,session 1查詢,在數據庫中存儲錯誤,但顯示正確。
插入過程:
”中國“兩字在客戶端操作系統字符集ZHS16GBK中的編碼是”d6,d0,b9,fa",由於NLS_LANG和數據庫字符集相同,數據庫端對客戶端傳過來的字符編碼不進行任何轉換直接存入數據庫,因此數據庫中存儲的編碼也是“d6,d0,b9,fa”,
讀取過程:
數據庫端讀取的編碼是“d6,d0,b9,fa”,由於NLS_LANG和數據庫字符集相同,客戶端對數據庫端傳過來的字符編碼不進行任何轉換直接顯示,編碼”d6,d0,b9,fa“在客戶端操作系統字符集ZHS16GBK對應的漢字為“中國”。
從以上分析可知,雖然讀取時正確的,但那是因為負負得正,實際上數據庫中存儲是錯誤的,因此要特別小心這種情況,在生成庫中要避免。其實只要對它進行length操作就能輕易揭開它的假面具:
得出的長度居然為3!實際的長度只是2,這會帶來很多麻煩。
場景2:session 1插入,session 2查詢,在數據庫中存儲錯誤,顯示也錯誤。
插入過程和場景1一樣,這裡就不再累述。
讀取過程:
數 據庫端讀取的編碼是“d6,d0,b9,fa”,由於NLS_LANG和數據庫字符集不同,客戶端對數據庫端傳過來的字符編碼進行轉換,數據庫端字符集 AL32UTF8裡編為“d6,d0,b9,fa”無法在客戶端操作系統字符集ZHS16GBK裡找到對應的編碼,所以只好用?代替。
場景3:session 2插入,session 1查詢,在數據庫中存儲正確,但顯示錯誤。
插入過程:
” 中國“兩字在客戶端操作系統字符集ZHS16GBK中的編碼是”d6,d0,b9,fa",由於NLS_LANG和數據庫字符集不同,Oracle會進行 字符編碼轉換,也就是將字符集ZHS16GBK裡“中國”的編碼“d6,d0,b9,fa"轉換為字符集"AL32UTF8"裡”中國“的編 碼”e4,b8,ad,e5,9b,bd“。
讀取過程:
數據庫端讀取的編碼 是”e4,b8,ad,e5,9b,bd“,由於NLS_LANG和數據庫字符集相同,客戶端對數據庫端傳過來的字符編碼不進行任何轉換直接顯示,編 碼”e4,b8,ad,e5,9b,bd“在客戶端操作系統字符集ZHS16GBK對應的漢字為“涓 浗”(原本2個字符,現在變成了3個字符,因為ZHS16GBK的漢字以2個字節編碼)。
場景4:session 2插入,session 2查詢,在數據庫中存儲正確,顯示也正確。
插入過程和場景3類似。
讀取過程:
數 據庫端讀取的編碼是”e4,b8,ad,e5,9b,bd“,由於NLS_LANG和數據庫字符集不同,客戶端對數據庫端傳過來的字符編碼進行轉換,數據 庫端字符集AL32UTF8裡”中國“兩字的編碼”e4,b8,ad,e5,9b,bd“轉換成客戶端操作系統字符集ZHS16GBK裡“中國”兩字的編 碼“d6,d0,b9,fa",並正常顯示。
這種情況雖然經過了兩次轉換,都確實最正確、最推薦的方式。
附錄:Oracle 字符集超集和子集的對應關系可查看:http://download.oracle.com/docs/cd/B19306_01/server.102/b14225/applocaledata.htm#sthref1988
結論:NLS_LANG只和客戶端操作系統的字符集相關,如果客戶端操作系統的字符集和數據庫字符集間無法正確轉換,則應該首先改變客戶端終端的字符集,而不是簡單地把NLS_LANG設為和數據庫字符集一樣。