遷移ORACLE資料到MogDB/openGauss時的字符集問題

T1YSL發表於2023-11-07

一、問題概述

ORACLE資料庫在儲存資料的時候,有時候會存在這樣一種現象,一張表裡的資料,既存在UTF8字元的,也存在GBK字元的,同時還有可能存在亂碼資料。

NLS_CHARACTERSET是資料庫字符集,NLS_NCHAR_CHARACTERSET是國家字符集,NLS_LANG 是 Oracle 資料庫客戶端的一個環境變數。

這種問題在於ORACLE對於規定了一種字符集後,對於插入的資料並沒有強校驗(garbage-in–garbage-out)。但是對於PG系的資料庫,資料庫的字符集在最開始建立資料庫的時候就指定了,而且一般情況下其中的表資料受字符集的嚴格校驗(SQL_ASCII除外)。

這也就導致了,原本是什麼型別的字元正常可以匯入到對應的字符集的庫裡,而原本直接存入ORACLE庫裡的和表原本的字符集不匹配的其他型別的字元可能無法轉換,此外亂碼資料可能直接不能轉換到PG系的庫裡。(ORACLE資料庫可以儲存亂碼資料,PG係資料庫有嚴格校驗)

一些常見場景如下:

1)如果恰巧資料庫的字符集也是UTF8, 那麼Oracle就不作任何轉換直接插入到資料中.

2)如果指定NLS_LANG是utf8, 但是輸入的卻是zhs16gbk的編碼, 那麼Oracle也會不作任何轉換, 將ZHS16GBK的字元編碼直接存入資料庫

3)如果資料庫的字元是AL32UTF8, 您指定NLS_LANG為ZHS16GBK, 但是, 您真正輸入的是UTF8的字元, 那麼,Oracle會把您輸入的UTF8字元當作ZHS16GBK字元轉換為UTF8存入資料庫. 這種情況會出現亂碼。

4)工具(putty/securecrt等等各種SSH客戶端等等)設定的字符集可能導致亂碼或者編碼轉換,客戶端字符集 NLS_LANG 和 個人工具顯示的字符集應該一致。

二、問題處理方案

1、使用SQL_ASCII字符集的資料庫

如果不考慮中文顯示以及亂碼顯示,可以直接在目標端建一個SQL_ASCII字符集的資料庫,那麼所有的資料均可以匯入到新的PG系的資料庫裡,這樣就不會有編碼轉換。這個設定基本不用來宣告所使用的編碼,因為此宣告會忽略編碼。在大多數情況下,如果使用了任何非ASCII資料,那麼我們不建議使用SQL_ASCII,因為openGauss/MogDB無法轉換或者校驗非ASCII字元。

2.根據原庫的編碼,建立相同字符集編碼的資料庫

ORACLE在建立資料庫的時候,需要指定字符集。雖然上述的一些原因導致ORACLE在指定了一種字符集的資料庫後,還可能存在其他型別的字元和亂碼資料,但這明顯是不符合規範的,因為拋開其他型別的資料,單單亂碼資料就不能算作有效資料,而且其他型別的資料,屬於違反了最初的規劃,也不能算作正常的資料。

因為不同的字元型別,對應儲存的不同的位元組長度是不一樣的,例如一個漢字,在UTF8裡儲存為3位位元組,而在GBK裡儲存為2個位元組。

比如一個“你”字,用UTF-8儲存在ORACLE資料裡,是佔用3個位元組,是“\xe4bda0”
而用GBK儲存在ORACLE資料裡,是佔用2個位元組,是“\xc4e3”

所以長度是不一致的,在它轉換到PG系的資料庫裡時,如果不能分辨出對應的是哪種型別的字元的話,只根據固定的一種型別的字符集的話,會存在部分資料的編碼無法轉換,在使用MTK(雲和恩墨的遷移工具)遷移資料的時候,可能類似於這種的報錯。
image.png

對於這種報錯,因為本身在原庫寫入的時候就存在問題,如果資料庫層大量修正的話,會耗費很大精力,因此,建議規範處理流程,僅根據資料庫的字符集遷移“正確”的資料,其餘的有問題的資料,可以在遷移過程找到對應的位置,或者進一步判斷出該類資料原本應該屬於什麼樣的字符集,然後這些問題資料讓應用自行修正。

(1)查詢儲存的編碼

在遇到問題資料的時候,我們可以根據其他正常的列,定位到對應的資料行,使用ORACLE的dump函式,檢視其在ORACLE資料庫裡儲存的編碼格式

select dump(sms_content,1016) code,sms_content from  tab_xxx where mo_id=‘123123123132312’
CODE						   SMS_CONTENT
-------------------------------------------------- ----------------------------------------------------------------------------------------------------
Typ=1 Len=64 CharacterSet=ZHS16GBK: 78,20,20,20,20 x             :(;                    "“           ¥    ”€“"
,20,20,20,20,20,20,20,20,20,3a,28,a3,bb,20,20,20,2
0,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
22,a1,b0,20,20,20,20,20,20,20,20,20,20,20,a3,a4,20
,20,20,20,a1,b1,80,a1,b0,22

(2)根據函式或者工具檢驗出真實的字元編碼

然後可以把有問題的資料對應的編碼,根據不同位數進行轉換,看轉換的資料是否是正常可讀的資料,原本最開始查詢的資料不用額外關注,因為可能受本地終端字符集的影響,可能本身儲存的是正常的,例如,我查詢到了問題列的資料匯出的結果是如下。
Pasted Graphic 4.png

取出部分編碼,進行轉換,"\xe59089e69e97"根據GBK按照兩位位元組的形式進行轉換,得到的是“鍚夋灄”,而根據UTF-8的型別進行轉換,得到的是“吉林”。因此原本這條資料本身應該是UTF-8的字符集。

可以使用相關函式進行轉換,也可以使用相關線上工具,例如:

postgres=# select convert_from(decode('e59089e69e97','hex')::bytea,'utf-8');
 convert_from 
--------------
 吉林
(1 row)

而本身這條資料正確顯示的時候,應該為 Pasted Graphic 3.png

根據此類方法,以及對業務資料的瞭解,可以分辨出原本資料應該屬於的字符集型別和非亂碼的資料。

(3)MTK遷移過程需要處理的部分

手動使用COPY處理

因為MTK遷移的過程可以指定每批次匯入資料的條數,在匯入資料存在此類字符集編碼問題的時候,我們一般會帶上igErrorData引數,會把有問題的整個批次的資料全部回滾,而其餘資料繼續入庫。
這個引數會在回滾那個問題批次資料的同時,把這個批次的資料記錄到錯誤檔案裡,產生csv檔案.

需要注意的是,這個csv是MTK分的遷移批次,每個批次的資料條數是一致的,可能在這一個批次裡,僅存在一條問題資料,但它會把整個批次的資料全部放到csv裡。可以先自行檢視csv篩出問題資料,或者定位問題資料。

這個時候,我們關注的是這個csv裡的正常資料,這個csv可以使用copy命令進行繞入。問題資料也可以暫時性先倒入,但是我們不關注因為字符集問題導致的查詢亂碼現象。

後續可以使用openGauss/MogDB的copy帶有的COMPATIBLE_ILLEGAL_CHARS引數,這個引數允許匯入非法字元容錯引數。此語法僅對COPY FROM匯入有效。但是在匯入這些存在問題的資料的時候,存在一個規則,即對於’\0’,容錯後轉換為空格,其他非法字元,容錯後轉換為問號。這部分資料即我們上文提到的屬於原庫就錯誤寫入的,後續應該讓業務修正。

image.png

MTK自動處理

MTK(v2.9.4)新增的compatibleIlLegaLChars選項決定了mogdb/openGauss使用MTK遷移過程裡,copy是否帶COMPATIBLE_ILLEGAL_CHARS選項。

如果開啟後,則會直接進行入上述的轉換方式,寫入資料,當然問題資料需要業務修正。(一般建議手動先產生錯誤的資料的csv後,定位有問題資料的位置後,再重新做一次自動處理的遷移)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69990629/viewspace-2993182/,如需轉載,請註明出處,否則將追究法律責任。

相關文章