数据迁移时如果遇到目标库使用不相同的字符集 应该如何是好?

标准的迁移肯定是在新建目标端的时候选择和原库环境相同的字符集建库,但是如果只是为了迁移部分数据到现有的其他库,但偏偏字符集还不同,应该如何做呢?

直接导出导入时肯定不合理的,也许有人知道数据泵可以自动转换字符集,是的,数据泵相对于传统exp/imp,在导入数据的时候是可以自动转换字符集,but 如果原库的字符在目标库中
压根不存在那是无论如何也无法存进去的,如 将中文字符存到只有英文字母的西方字符集中,那根本没有对应的字符来表示中文,那肯定是乱码。

所以如果字符集不同,我们一定要先检查原库中哪些数据是有可能丢失的,哪些数据是需要先进行处理的。也许有认为,我子集直接导入超级应该没问题,这点我不敢苟同,也不适合大部分的使用
场景。

做好事前检查还是有必要的。

关于检查我们用到的工具是csscan,具体的命令语法网上一搜一堆,我不详细介绍了,最简单的直接运行csscan然后按照提示交互操作即可。

扫描完后会形成三个文件在当前目录 scan.txt、scan.err 、scan.out

在scan.txt中会看到类似:


[Application Data Conversion Summary]


Datatype                    Changeless      Convertible       Truncation            Lossy
--------------------- ---------------- ---------------- ---------------- ----------------
VARCHAR2                   944,131,446      473,535,015        1,126,057               22
CHAR                       783,548,321        1,401,325                0                0
LONG                                 0                0                0                0
VARRAY                               0                0                0                0
--------------------- ---------------- ---------------- ---------------- ----------------
Total                    1,727,679,767      474,936,340        1,126,057               22
Total in percentage             78.398%          21.551%           0.051%           0.000%

 其中
Convertible    是可以自动转换的
  Truncation          表示会被截断的
   Lossy
             表示不被新字符集支持的

我们需要处理的是 Truncation 和 lossy类型的数据。

对于Truncation 是因为数据导入到新字符集中后,数据字符长度会超过原列的最大值而出现截断的情况,如果直接导入数据是会损坏的,该如何做呢?

csscan扫描完后执行如下sql 来检查哪些表列的上限需要提升

点击(此处)折叠或打开

  1. SET serveroutput ON size 200000
  2. DECLARE
  3. v_Stmt1 VARCHAR2(6000 BYTE);
  4. BEGIN
  5. DBMS_OUTPUT.PUT_LINE('output is: ');
  6. DBMS_OUTPUT.PUT_LINE('owner.table_name column_name - column_type - max expansion in Bytes.');
  7. DBMS_OUTPUT.PUT_LINE('....................................................................');
  8. FOR rec IN
  9. (select u.owner_name, u.table_name, u.column_name, u.column_type, max(max_post_convert_size) post_convert_size FROM csmig.csmv$columns u
  10. WHERE u.exceed_size_rows > to_number('0') GROUP BY u.owner_name, u.table_name, u.column_name, u.column_type ORDER BY u.owner_name, u.table_name, u.column_name)
  11. LOOP
  12. IF rec.post_convert_size <= '2000' and rec.column_type = 'CHAR' THEN
  13. DBMS_OUTPUT.PUT_LINE( rec.owner_name ||'.'|| rec.table_name ||' ('|| rec.column_name ||') - '|| rec.column_type ||' - '|| rec.post_convert_size || ' Bytes .');
  14. END IF;
  15. IF rec.post_convert_size > '2000' and rec.column_type = 'CHAR' THEN
  16. DBMS_OUTPUT.PUT_LINE( rec.owner_name ||'.'|| rec.table_name ||' ('|| rec.column_name ||') - '|| rec.column_type ||' - '|| rec.post_convert_size || ' Bytes .');
  17. dbms_output.put_line('! Warning !');
  18. dbms_output.put_line('Data in '|| rec.owner_name ||'.'|| rec.table_name ||'.'|| rec.column_name ||' will expand OVER the datatype limit of CHAR !');
  19. IF rec.post_convert_size > '4000' THEN
  20. dbms_output.put_line('The '||rec.column_name ||' CHAR column NEED to change to CLOB to solve the truncation!');
  21. END IF;
  22. IF rec.post_convert_size <= '4000' THEN
  23. dbms_output.put_line('The '||rec.column_name ||' CHAR column NEED to change to VARCHAR2 to solve the truncation!');
  24. END IF;
  25. dbms_output.put_line('Using CHAR semantics for this column without changing datatype will NOT solve the truncation!');
  26. END IF;
  27. IF rec.post_convert_size <= '4000' and rec.column_type = 'VARCHAR2' THEN
  28. DBMS_OUTPUT.PUT_LINE( rec.owner_name ||'.'|| rec.table_name ||' ('|| rec.column_name ||') - '|| rec.column_type ||' - '|| rec.post_convert_size ||' Bytes .');
  29. END IF;
  30. IF rec.post_convert_size > '4000' and rec.column_type = 'VARCHAR2' THEN
  31. DBMS_OUTPUT.PUT_LINE( rec.owner_name ||'.'|| rec.table_name ||' ('|| rec.column_name ||') - '|| rec.column_type ||' - '|| rec.post_convert_size || ' Bytes .');
  32. dbms_output.put_line('! Warning !');
  33. dbms_output.put_line('Data in '|| rec.owner_name ||'.'|| rec.table_name ||'.'|| rec.column_name ||' will expand OVER the datatype limit of VARCHAR2 !');
  34. dbms_output.put_line('The '||rec.column_name ||' VARCHAR2 column NEED to change to CLOB to solve the truncation!');
  35. dbms_output.put_line('Using CHAR semantics for this column without changing datatype will NOT solve the truncation!');
  36. END IF;
  37. END LOOP;
  38. END;
  39. /
输出结果如:

output is:                                                                     

owner.table_name column_name - column_type - max expansion in Bytes.           

....................................................................           

XXX. RDGATHER (NOTE) - VARCHAR2 - 69 Bytes .                  

XXX. ARPAYMENT (SERGROUP) - VARCHAR2 - 33 Bytes .                    

           
表示XXX. RDGATHER (NOTE)的列宽度需要提升到最少69,才能不被截断。(现有行中该列转换后的最长长度达到了69) 当然实际操作中要预留,所以导出前要先把  RDGATHER (NOTE) 修改,如设置值120.

全部设置完毕后,重新运行csscan检测。确定没有被截断项。 
当然也可以在新库中先创建指定的空表,并设置该列值为120 ,然后再进行数据的追加导入,但是这就没有办法进行验证了。

是什么原因导致出现的截断呢?

举例 同样是中文,在16gbk字符集中一个汉字是2个字节,在utf8中一个汉字要占用3个字节,所以当从16gbk导入到utf8时字符长度就自动增加了,如果增加后超过了原列的上限则就会出现数据截断。


处理完以上Truncation 导入时依然有可能会出现:

ORA-12899: value too large for column OBUPIDER (actual: 18, maximum: 16)



一种情况是当提示的列是char类型的,char(5)和varchar2(5)的却别是 char(5)如果只是存储一个字符a ,那剩余4个字符会用空白字符填充。 而varchar2(5)是动态的,a只占一个字符。那如果这种情况下 char(5)存放的是一个汉字‘我’呢?  原来 char(5)中是  2个字节+3个空白字节,导入到新环境后我就需要3个字节,那就是3个字节+3个空白字节,然后就需要6个字节才能存放,显然超过了char(5)的长度。

所以两种方法, 
1、将原列的char类型改成varchar2 

alter table   CHARGE modify OPERATOR VARCHAR2(20 byte);


2、新端先创建上表,并调整好列长度,然后追加数据。

更喜欢第一种, 第一种设置完毕后还要清除转换后字符后面保留的空白

update scott.test set col= rtrim(col);
commit;

然后再执行导出、导入。

另外如果表的列名中同样存在中文,转换后也会存在2字节变3字节的情况,但是列名是有字符上限的,oracle数据库列名最长定义是30个字符。
所以也有可能提示ORA-12899: value too large for column OBUPIDER ,引出需要先修改列名然后再导出。
至于为什么是30个字符呢,就是这么规定的,应该和字典视图中的列名名称那一列的规定字符长度上限有关吧。

SQL> desc DBA_TAB_COLUMNS 
 Name   Null?    Type
 ----------------------------------------- -------- ----------------------------
 OWNER   NOT NULL VARCHAR2(30)
 TABLE_NAME   NOT NULL VARCHAR2(30)
 COLUMN_NAME   NOT NULL VARCHAR2(30)



至于别标记为Lossy的行(可以查看scan.err来确认哪些行),则是有可能出现字符不兼容而出现乱码的,可以尝试通过plsql 、sqldevelop等工具打开表然后保存 处理 插入新库。



参考文档:
CSSCAN does not detect data truncation for CHAR datatype - ORA-12899 when importing (文档 ID 779526.1)


ORA-01401 / ORA-12899 / ORA-01461 While Importing Or Loading Data In An AL32UTF8 / UTF8 (Unicode) Or Other Multibyte NLS_CHARACTERSET Database. (文档 ID 1297961.1)


Script to Solve Truncation in Csscan output by Shortening Data Before Conversion (文档 ID 1231922.1)


https://support.oracle.com/epmos/faces/KMConsolidatedSearch?_adf.ctrl-state=18fxno7duu_4&_afrLoop=422947266727441
csscan can not char type

请使用浏览器的分享功能分享到微信等