經常看到壹些朋友問關於ORACLE字符集的問題,我想用叠代的方式介紹壹下。
第壹叠代:掌握字符集的基本概念。
有些朋友可能會認為這是多此壹舉,但事實上,正是因為沒有掌握相關的基本概念,才導致了許多問題和疑問。
首先是字符集的概念。
我們知道,電子計算機最初是用來進行科學計算的(所以稱為 "計算機"),但隨著技術的發展,需要計算機來進行其他方面的應用處理。這就要求計算機不僅要處理數值,還要處理其他信息,如文字、特殊符號等,而計算機本身能直接處理的只有數值信息,所以要求對這些文字、符號、信息進行數值編碼,最初的字符集是我們非常熟悉的ASCII,它是用7位二進制數字來表示128個字符,後來隨著不同國家和組織的需要,出現了很多很多字符。字符集有很多,如西歐字符的 ISO8859 系列字符集、漢字的 GB2312-80、GBK 等字符集。
字符集的實質是將壹組特定的符號,分別賦予不同的數字編碼,以方便計算機處理。
字符集之間的轉換。字符集多了,就會帶來壹個問題,比如壹個字符,在某個字符集中被編碼為壹個數值,而在另壹個字符集中又被編碼為另壹個數值,例如,我來創建兩個字符集 demo_charset1 和 demo_charset2 ,在 demo_charset1 中,我指定三個符號的編碼為:在 demo_charset1 中,我指定三個符號的編碼為:A ( 0001)、B (0010)、? (1111);而在 demo_charset2 中,我也指定三個符號的編碼為:A (1001)、C (1111)、?此時,我的任務是編寫壹個程序,負責在 demo_charset1 和 demo_charset2 之間進行轉換。由於我知道這兩個字符集的編碼規則,因此對於 demo_charset1 中的 0001,在轉換到 demo_charset2 時,其編碼應改為 1001;對於 demo_charset1 中的 1111,在轉換到 demo_charset2 時,其值保持不變;而對於 demo_charset1 中的 0010,它對應的字符是 B,但在 demo_charset2 中沒有對應的字符,因此理論上無法轉換,對於所有這些無法轉換的情況,我們可以將它們統壹為目標字符集中的壹個特殊字符(稱為 "替換字符"),例如這裏 我們可以使用 ?作為替代字符,因此 B 將被轉換為 ?,但會損失信息;同樣,將 C 字符從 demo_charset2 轉換為 demo_charset1 時也會損失信息。
因此,在字符集轉換過程中,如果源字符集中的某個字符在目標字符集中沒有定義,就會造成信息丟失。
數據庫字符集的選擇。
在創建數據庫時,我們需要考慮的問題之壹是選擇什麽字符集和國家字符集(由 CREATE DATABASE 中的 CHARACTER SET 和 NATIONAL CHARACTER SET 子句指定)。考慮到這個問題,我們必須明確數據庫中需要存儲哪些數據,如果只需要存儲英文信息,那麽選擇 US7ASCII 作為字符集即可;但如果要存儲中文,那麽我們就需要選擇可以支持中文的字符集(如 ZHS16GBK);如果需要存儲多語言文本,那麽就需要選擇 UTF8。
數據庫字符集的確定,實際上是對數據庫所能處理的字符集合及其編碼的確定,因為字符集選定後再更改會有壹些限制,所以在創建數據庫前壹定要考慮清楚再選擇。
很多朋友在創建數據庫時,沒有考慮清楚,往往會選擇壹個默認的字符集,如 WE8ISO8859P1 或 US7ASCII,而這兩個字符集是不對漢字進行編碼的,所以使用這種字符集來存儲漢字信息從原理上來說是錯誤的。雖然有些時候這種字符集看似工作正常,但卻給數據庫的使用和維護帶來了壹系列麻煩,我們將在後面的叠代中深入分析。
客戶端字符集。
有過壹定 Oracle 使用經驗的朋友大多知道如何通過 NLS_LANG 設置客戶端,它由以下部分組成:NLS_LANG=<語言>_<領土>。<客戶端字符集>,其中第三部分<客戶端字符集>旨在指示客戶端操作系統使用的默認字符集。所以根據正式使用時的情況,NLS_LANG 應根據客戶機的實際情況進行配置,尤其是對於字符集更是如此,這樣 Oracle 就能最大限度地實現數據庫字符集與客戶機字符集的自動轉換(當然,如果需要轉換的話)。
總結第壹次叠代的亮點:
字符集:將特定的符號集編碼為計算機可處理的值;
字符集之間的轉換:對於源字符集和目標字符集中都存在的符號,理論上轉換不會導致信息丟失;對於源字符集中存在但目標字符集中不存在的符號,理論上轉換會導致信息丟失;
字符集之間的轉換:
數據庫字符集:選擇包含要存儲信息的所有符號的字符集;
客戶端字符集設置:指定客戶端操作系統默認使用的字符集。
第二次叠代:舉例加深對基本概念的理解
下面我將引用網友 tellin 在 ITPUB 上發表的 "字符集研究與疑問 "的帖子,這位朋友在帖子中列舉了他所做過的相關實驗,並就實驗結果提出了壹些疑問,我將對他的實驗結果進行分析。我將對他的實驗結果進行分析,並回答他的問題。
實驗結果分析壹
quote:
--------------------------------------------------------------------------------
原作者:tellin 發表於
將客戶端字符集設置為 US7ASCII
D:\>SET NLS_LANG=AMERICAN_AMERICA.US7ASCII
查看服務器字符集為 US7ASCII
SQL> SELECT * FROM NLS_ DATABASE_PARAMETERS;
PARAMETER VALUE
------------------------------ ----------------------------------------
NLS_ CHARACTERSET US7ASCII
CREATE TABLE TEST
SQL> CREATE TABLE TEST (R1 VARCHAR2(10));
創建表。
輸入數據
SQL> INSERT INTO TEST VALUES('Northeast');
創建 1 行。
SQL> SELECT * FROM TEST;
R1
----------
Northeast
SQL> EXIT
--------------------------------------------------------------------------------
這部分實驗數據的訪問和顯示都是正確的,好像沒有什麽問題,但實際上存在很大的隱患。
首先,要在數據庫中存儲漢字,將數據庫字符集設置為 US7ASCII 是不合適的,因為 US7ASCII 只定義了 128 個符號,不支持漢字。此外,既然可以在 SQL*PLUS 中輸入中文,那麽操作系統應該默認支持中文,但將 NLS_LANG 中的字符集設置為 US7ASCII 顯然也是不正確的,它並不能反映客戶端的實際情況。
但實際顯示是正確的,這主要是因為 Oracle 檢查數據庫和客戶端的字符集設置是否壹致,那麽數據在客戶端和數據庫之間的訪問過程中就不會發生任何轉換。具體來說,在客戶端中輸入 "東北"、"東 "的漢字編碼為 182(10110110)、171(10101011),"北 "的漢字編碼為 177(10110001)、177(10110001),這樣就會不加任何改動地存入數據庫,但這實際上導致了數據庫中標識的字符集與實際存入的字符集不壹致,這在某種意義上也是壹種不壹致,也是壹種錯誤。在 SELECT 的過程中,Oracle 也會檢查發現數據庫和客戶端的字符集是壹致的,因此也會將存入客戶端的內容原封不動地傳輸,而客戶端操作系統也會識別出這是壹個漢字編碼,從而使其能夠正確顯示。
在這個示例中,數據庫和客戶端設置都存在問題,但似乎起到了 "負負得正 "的效果,從應用程序的角度看似乎沒有問題。不過,潛在的問題也很大,比如在應用 length 或 substr 等字符串函數時,可能會得到意想不到的結果。此外,如果遇到導入/導出(import/export)就會遇到更多麻煩。壹些朋友在這方面做了很多測試,如 eygle 研究了 "源數據庫字符集為 US7ASCII,導出文件字符集為 US7ASCII 或 ZHS16GBK,目標數據庫字符集為 ZHS16GBK "的情況,他得出的結論是 "如果是在 Oracle92 中導入,那麽導出文件字符集為 US7ASCII 或 ZHS16GBK,目標數據庫字符集為 ZHS16GBK"、針對這種情況,我們可以使用 Oracle8i 的導出工具將導出字符集設置為 US7ASCII 或 ZHS16GBK,將目標數據庫字符集設置為 ZHS16GBK。對於這種情況,我們可以使用 Oracle8i 導出工具將導出字符集設置為 US7ASCII,然後在導出後修改第二和第三個字符,修改 0001 至 0354,這樣就可以將 US7ASCII 字符集的數據正確導入到 ZHS16GBK 的數據庫中"。我想對於這些結論,這樣理解可能更合適:因為 ZHS16GBK 字符集是壹個超級 US7ASCII,所以如果正常操作的話,這種轉換應該是沒有問題的;但問題的本質是,我們應該將只存儲英文字符的 US7ASCII 數據庫,非常規存儲中文信息,那麽在轉換過程中出現錯誤或麻煩就沒什麽奇怪的了,但沒有麻煩就有點奇怪了。
所以要避免這種情況,就必須在創建數據庫時選擇合適的字符集,以免出現標簽(數據庫的字符集設置)與實際(數據庫中實際存儲的信息)不符的情況。
實驗結果分析二
引用:
--------------------------------------------------------------------------------
[ 將客戶端字符集更改為 ZHS16GBK
D:\>SET NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
D:\>SQLPLUS "/ AS SYSDBA"
無法正常顯示數據
SQL> SELECT * FROM TEST;
R1
--------------------
6+11
問題 1:ZHS16GBK 是 US7ASCII 的超集,為什麽不能在 ZHS16GBK 環境中正確顯示
-------------- ------------------------------------------------------------------
這主要是因為 Oracle 檢查並發現數據庫設置的字符集與客戶端配置的字符集不同,因此會將數據轉換為該字符集。實際存儲在數據庫中的數據是 182 (10110110)、171 (10101011)、177 (10110001)、177 (10110001),由於數據庫字符集設置為 US7ASCII,這是壹個存儲在 8 位字節中的 7 位字符集,那麽 Oracle 就會忽略各個字節的最高位、那麽 182(10110110)就變成了 54(0110110),在 ZHS16GBK 中代表數字符號 "6"(當然,在其他字符集中也是 "6"),同樣的過程也發生在其他三個字節中,這樣 "東北 "就變成了 "6+11"。
實驗結果分析三
quote:
--------------------------------------------------------------------------------
Originally posted by tellin 發表於
使用 ZHS16GBK 插入數據
SQL> INSERT INTO TEST VALUES('Northeast');
1 條記錄已創建。
SQL> SELECT * FROM TEST;
R1
-- ------------------
6+11
SQL>;EXIT
------------------------------------------------------------------------ --------
當客戶端的字符集設置為 ZHS16GBK,然後在數據庫中插入 "東北 "時,Oracle 檢查發現數據庫的字符集設置為 US7ASCII,與客戶端的不壹致,需要進行轉換,但字符集 ZHS16GBK 中的 "東北 "兩個字符不壹致。"但是,在 US7ASCII 中沒有與字符集 ZHS16GBK 相對應的字符,因此 Oracle 在數據庫中插入了統壹的 "替換字符",這裏是"?",編碼為 63 (00111111)。,編碼為 63 (00111111),此時,輸入的信息實際上已經丟失,無論字符集設置如何更改(如下面引用的實驗結果),第二行 SELECT 出來的結果也是兩個"?"符號(註意是兩個,而不是四個)。
quote:
--------------------------------------------------------------------------------
將客戶端字符集更改為 US7ASCII
D:\>SET NLS_LANG=AMERICAN_AMERICA.US7ASCII
D:\>SQLPLUS"/AS SYSDBA"
無法顯示用 ZHS16GBK 插入的字符集,但可以顯示用 US7ASCII 插入的字符集
SQL> SELECT * FROM TEST;
R1
----------
Northeast
將服務器字符集更改為 ZHS16GBK
SQL> update props$ set value$=' ZHS16GBK' WHERE NAME='NLS_CHARACTERSET';
更新了 1 行。
SQL> COMMIT;
將客戶端字符集更改為 ZHS16GBK
D:\> SET NLS_LANG= AMERICAN_AMERICA.ZHS16GBK
D:\>SQLPLUS "/ AS SYSDBA"
可以顯示以前的 US7ASCII 字符集,但不能顯示用 ZHS16GBK 插入的數據,這意味著用 ZHS16GBK 插入的數據是亂碼。
SQL> SELECT * FROM TEST;
R1
--------------------
Northeast
-------------------------------------------- ------------------------------------
需要註意的是,通過 "update props$ set value$='ZHS16GBK' WHERE NAME='NLS_CHARACTERSET';"來修改數據庫字符集是非常規的,很可能會引起問題,這裏只是引用了原來的實驗結果。
實驗結果分析四
引用:
--------------------------------------------------------------------------------
SQL> INSERT INTO TEST VALUES('Northeast');
1 row created.
SQL> SELECT * FROM TEST;
R1
--------------------
Northeast
Northeastern
< p>Northeast
SQL>;EXIT
--------------------------------------------------------------------------------
由於此時數據庫和客戶端的字符集設置都是 ZHS16GBK,因此沒有進行字符集轉換,第壹行和第三行數據顯示正確,而第二行顯示為"?",因為存儲的數據是 63 (00111111)。不是。
quote:
--------------------------------------------------------------------------------
將客戶端字符集更改為 US7ASCII
D:\>SET NLS_LANG=AMERICAN_AMERICA.US7ASCII
D:\>SQLPLUS "/ AS SYSDBA"
無法顯示數據
SQL> SELECT * FROM TEST;
R1<
----------
問題 2:第壹行數據是使用 US7ASCII 環境插入的,為什麽無法正常顯示?
--------------------------------------------------------------------------------
SELECT 將客戶端字符集設置更改為 US7ASCII 後,Oracle 檢查發現數據庫設置的字符集為 ZHS16GBK,需要將數據轉換為該字符集,而第壹行和第三行的漢字 "東 "和 "北 "在客戶端字符集 US7ASCII 中沒有對應的字符,因此轉換為 "替換字符"。"替換字符"("?)數據庫中存儲的第二行數據原本是兩個"? "號,因此雖然客戶端中顯示的三行都是兩個"? "號,但數據庫中存儲的內容是不同的。
實驗結果分析五
引用:
--------------------------------------------------------------------------------
SQL> INSERT INTO TEST VALUES('Northeast');
1 row created.
SQL> EXIT
將客戶端字符集更改為 ZHS16GBK
D:\> SET NLS_LANG=AMERICAN_AMERICA .ZHS16GBK
D:\>SQLPLUS"/AS SYSDBA"
無法顯示用 US7ASCII 插入的字符集,但可以顯示用 ZHS16GBK 插入的字符集
SQL> SELECT * FROM TEST;
R1
--------------------
Northeast
Northeast
6+11
SQL>
問題 3:US7ASCII 是 ZHS16GBK 的子集,為什麽插入的數據不能在 US7ASCII 環境中顯示?[/B]
--------------------------------------------------------------------------------
當客戶端字符集設置為 US7ASCII 時,在字符集為 ZHS16GBK 的數據庫中插入 "東北 "數據時,需要進行字符轉換,"東北 "的 ZHS16GBK 編碼為 182 (10110110)、171 (10101011) 和 177 (10110001)。10110001)、177(10110001),由於采用 US7ASCII 7 位編碼,Oracle 會將這兩個漢字作為四個字符處理,並忽略每個字節的最高位,這樣存儲在數據庫中的編碼就變成了 54(00110110)、43(00101011)和 49(00110001)、49(00110001),即 "東北 "的 ZHS16GBK 編碼為 182(10110110)、171(10101011)和 177(10110001)。00110001),即 "6+11",原有信息已被更改。此時,將客戶端字符集設置為 ZHS16GBK 再進行 SELECT,數據庫中的信息就不需要更改了,到了客戶端,前三行由於存入的信息沒有更改,顯示為 "東北",而第二行和第四行由於插入數據時信息被更改,所以無法顯示原來的信息。
分析了這麽多內容,其實總結起來很簡單
分析了這麽多內容,其實總結起來很簡單,為了在字符集方面少出錯、少麻煩,需要堅持兩個基本原則:
在數據庫方面:選擇所需的字符集(通過 CREATE DATABASE 中的 CHARACTER SET 和 NATIONAL CHARACTER SET 子句);
在客戶端:設置操作系統實際使用的字符集(通過環境變量 NLS_LANG 設置)。