直接导出导入时肯定不合理的,也许有人知道数据泵可以自动转换字符集,是的,数据泵相对于传统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 来检查哪些表列的上限需要提升
点击(此处)折叠或打开
-
SET serveroutput ON size 200000
-
DECLARE
-
v_Stmt1 VARCHAR2(6000 BYTE);
-
BEGIN
-
DBMS_OUTPUT.PUT_LINE('output is: ');
-
DBMS_OUTPUT.PUT_LINE('owner.table_name column_name - column_type - max expansion in Bytes.');
-
DBMS_OUTPUT.PUT_LINE('....................................................................');
-
FOR rec IN
-
(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
-
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)
-
LOOP
-
IF rec.post_convert_size <= '2000' and rec.column_type = 'CHAR' THEN
-
DBMS_OUTPUT.PUT_LINE( rec.owner_name ||'.'|| rec.table_name ||' ('|| rec.column_name ||') - '|| rec.column_type ||' - '|| rec.post_convert_size || ' Bytes .');
-
END IF;
-
IF rec.post_convert_size > '2000' and rec.column_type = 'CHAR' THEN
-
DBMS_OUTPUT.PUT_LINE( rec.owner_name ||'.'|| rec.table_name ||' ('|| rec.column_name ||') - '|| rec.column_type ||' - '|| rec.post_convert_size || ' Bytes .');
-
dbms_output.put_line('! Warning !');
-
dbms_output.put_line('Data in '|| rec.owner_name ||'.'|| rec.table_name ||'.'|| rec.column_name ||' will expand OVER the datatype limit of CHAR !');
-
IF rec.post_convert_size > '4000' THEN
-
dbms_output.put_line('The '||rec.column_name ||' CHAR column NEED to change to CLOB to solve the truncation!');
-
END IF;
-
IF rec.post_convert_size <= '4000' THEN
-
dbms_output.put_line('The '||rec.column_name ||' CHAR column NEED to change to VARCHAR2 to solve the truncation!');
-
END IF;
-
dbms_output.put_line('Using CHAR semantics for this column without changing datatype will NOT solve the truncation!');
-
END IF;
-
IF rec.post_convert_size <= '4000' and rec.column_type = 'VARCHAR2' THEN
-
DBMS_OUTPUT.PUT_LINE( rec.owner_name ||'.'|| rec.table_name ||' ('|| rec.column_name ||') - '|| rec.column_type ||' - '|| rec.post_convert_size ||' Bytes .');
-
END IF;
-
IF rec.post_convert_size > '4000' and rec.column_type = 'VARCHAR2' THEN
-
DBMS_OUTPUT.PUT_LINE( rec.owner_name ||'.'|| rec.table_name ||' ('|| rec.column_name ||') - '|| rec.column_type ||' - '|| rec.post_convert_size || ' Bytes .');
-
dbms_output.put_line('! Warning !');
-
dbms_output.put_line('Data in '|| rec.owner_name ||'.'|| rec.table_name ||'.'|| rec.column_name ||' will expand OVER the datatype limit of VARCHAR2 !');
-
dbms_output.put_line('The '||rec.column_name ||' VARCHAR2 column NEED to change to CLOB to solve the truncation!');
-
dbms_output.put_line('Using CHAR semantics for this column without changing datatype will NOT solve the truncation!');
-
END IF;
-
END LOOP;
-
END;
- /
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