1. 需求目的
源数据库NLS_CHARACTERSET为ZHT16MSWIN950,需将源数据库DMP导入到支持UNICODE的AL32UTF8数据库中(源数据库&目标数据库平台:Oracle 10.2.0.1.0)。

2. 导入过程中遇到的问题
将源数据库DMP导入目标数据库时,部分字段出现ORA-12899错误提示
unicode001.jpg

3. 问题分析
依据错误信息查看APLD的表结构,REMARK字段TYPE VARCHAR2(40)
unicode002.jpg

出现问题的REMARK内容值
unicode003.jpg

参照其它已构建好的UNICODE标准数据库中的APLD表结构,REMARK字段TYPE VARCHAR(40 CHAR)
unicode004.jpg

结合以上信息,需要测试一下这两种不同编码的数据库对中文字符的处理方式,首先是ZHT16MSWIN950:

SQL> select length('生產計劃之生產料號變更時產生') with_char, lengthb('生產計劃之生產料號變更時產生') with_byte from dual;

  WITH_CHAR    WITH_BYTE
  -----------   -----------
      14       28

接着是AL32UTF8:

SQL> select length('生產計劃之生產料號變更時產生') with_char, lengthb('生產計劃之生產料號變更時產生') with_byte from dual;

  WITH_CHAR    WITH_BYTE
  -----------   -----------
      14       42

不难看出,“生產計劃之生產料號變更時產生”为14个中文字符,一个中文字符在ZHT16MSWIN950编码的数据库中占用28/14=2个字节,而在AL32UTF8编码的数据库中会占用42/14=3个字节。源DMP中APLD.REMARK字段长度为VARCHAR2(40),“生產計劃之生產料號變更時產生”导入AL32UTF8编码的数据库时,字节长度将转换为42,因此系统出现ORA-12899的错误提示。

4. 处理过程
针对此问题,最初在网上看到有人建议使用加大源资料库字段后导出DMP的方法,其中可以使用下面的SQL生成批量修改语句:

select 'alter table ' || x.table_name || ' modify ' || x.column_name ||' varchar2('||x.data_length*2||');'
from user_tab_cols x WHERE data_type='VARCHAR2'

此方法的主要缺点是比较难确定加大data_length时需要乘以的倍数,经过考量之后,改用EXPDP/IMPDP进行处理,因为数据泵在导出导入的过程中会自动转换字段的长度。网上关于EXPDP/IMPDP的文档有很多,这边只提醒一下:在使用EXPDP/IMPDP时,除了要用create directory dump_dir as 'x:\dump_dir'建立dump目录之外,还需使用授权语句grant read, write on directory dump_dir to user,将读写dump目录的权限赋予执行EXP的DUMP USER。

之后使用EXPDP/IMPDP成功地将所有TABLE转入到了AL32UTF8编码的数据库当中,但革地打击。我收下衣物,挂在卧室客厅的衣橱里,每件衣服都隔着一定距离,并且,保持衣橱的门敞开。鼓楼区的西北处我租了命的道路总是曲折的,让人备受打击的是,在导入FUNCTION时发生了未知错误,网上查找后暂时无解,于是用IMPDP的EXCLUDE参数排除FUNCTION,结果更糟糕的状况发生了,PROCEDURE、VIEW、TRIGGER的导入接连出现同样的问题。

由于时间比较紧,需要快速处理,只好另寻其它的变通方法,考虑到全部TABLE已导入成功,而模式中FUNCTION、PROCEDURE、VIEW的数量并不是很多,且TRIGGER有提供trigger.lst进行批量重建,最后决定采取以下应急处理步骤:

1) 将所有已转换完成的TABLE用EXP导出到新的DMP,方法如下:

spool c:\alltables.txt
select table_name||',' from user_tables
spool off

将所有表名放入alltables.txt中,使用字处理软件将换行符去掉,接着将alltables.txt的内容加在EXP的TABLES参数中即可。

2) 重新执行IMP导入源DMP,虽然此方法不能导入完整的TABLE,但对于FUNCTION、 PROCEDURE、VIEW来讲是可行的,导入后使用下面的方法删除所有TABLE:

spool c:\droptables.sql
select 'drop table '||table_name||';' from user_tables;
spool off
@c:\droptables.sql

删除之后,IMPORT步骤1)中导出的新DMP就可以完成对所有TABLE的替换,此方法虽然很暴力,但至少在脑力上算是比较省事的 :-)

3) 由于部分TABLE的字段长度已进行转换,将FUNCTION、PROCEDURE、VIEW重新编译,执行 trigger.lst生成TRIGGER。

END

补充:目前还是比较担心这样的处理方式会造成遗漏,有待在应用程序端进行测试,如有错误,欢迎指正。

---------------------------------------------------------------------------------------------------

2008.08.04更新(问题已解决,感谢TANK的协助)

首先在这边先补充一下问题的前置状况,在导入源DMP文件之前,新建的AL32UTF8编码数据库已将PFILE和SPFILE中的NLS_LENGTH_SEMANTICS修改成CHAR,并重启服务器后才执行了导入的动作,但错误提示依旧,Oracle 并没有依照之前的设定值转换相对应的字段。

其实网上提供的加大data_length的方法已经很接近答案了,只需稍加改进,在ZHT16MSWIN950数据库导出DMP文件之前,执行下面的SQL即可完成正确的批量修改:

spool c:\tochar.sql
select 'ALTER TABLE ' || table_name || ' MODIFY ' || column_name || ' VARCHAR2(' || char_col_decl_length || ' CHAR);'
from user_tab_cols
where data_type = 'VARCHAR2' and char_used = 'B';
spool off
@ c:\tochar.sql

由于执行操作的数据库字段只有varchar2和number两种类型,在这边我们仅针对varchar2进行修改。收尾的步骤就比较简单了:导出DMP—建立AL32UTF8数据库—导入DMP。

Rand Posts:



我们在天上的父
愿人都尊你的名为圣
愿你的国降临
愿你的旨意行在地上如同行在天上
我们将顺着你的指引前行
直至重归你的梯下


7 条评论



  1. 我保持缄默……

    [ 引用 ]


  2. 问题解决 8)

    [ 引用 ]


  3. tank不是吕建中吧

    [ 引用 ]


  4. 难道你认为这个TANK是非他莫属,汗

    [ 引用 ]


  5. 原来以为要我出手的,看来没必要了

    [ 引用 ]


  6. 。。。。。。

    [ 引用 ]


  7. 请问procedure、package如何修改其参数为varchar2的类型,那么多如何批量修改????望赐教
    \n

    [ 引用 ]

发表评论

文明上网,共建和谐社会。