Oracle 19c升级实战:EXPDP迁移中的六大“坑”与填坑指南
1. 字符集转换导致的数据丢失问题
最近在帮客户做Oracle 11g升级到19c的项目时,遇到了一个让人头疼的问题:使用expdp/impdp工具迁移用户数据时,系统报错"ORA-39346: data loss in character set conversion"。这个错误看起来简单,但排查起来还真需要点技巧。
首先,我们需要理解这个错误的本质。当Oracle在导入数据时,如果发现源库和目标库的字符集不匹配,就会尝试进行字符集转换。在这个过程中,某些特殊字符可能会丢失或无法正确转换,系统就会抛出这个错误。我遇到的具体情况是,一些表注释中的中文字符在导入过程中出现了乱码。
排查这个问题的第一步,就是检查两边的字符集设置是否一致。你可以用这个SQL查询:
select property_name,property_value from database_properties where property_name like '%CHARA%'这个查询会返回数据库的字符集(NLS_CHARACTERSET)和国家字符集(NLS_NCHAR_CHARACTERSET)设置。在我的案例中,虽然两边的设置都是AL32UTF8和UTF8,看起来完全一致,但还是出现了问题。
深入分析后发现,问题出在源库的某些对象元数据上。这些对象的注释在源库中就已经存在乱码,只是11g版本下没有报错。当迁移到19c时,新版本对字符集检查更加严格,就暴露了这个问题。这种情况特别容易出现在那些历史悠久的系统上,可能是在某个历史时期导入数据时就已经埋下了隐患。
解决这个问题有几个思路:
- 如果乱码只出现在注释等非关键位置,可以暂时忽略,不影响系统运行
- 对于重要的注释内容,可以在迁移前先在源库中修正
- 最彻底的方案是重建这些对象,确保元数据干净
2. 作业(Job)导入后log_user异常问题
在完成数据导入后,我发现一个奇怪的现象:dba_jobs视图中的log_user字段全部显示为SYS,而不是原来的应用用户。这看起来像是权限问题,但实际原因要复杂得多。
刚开始我以为是使用SYS用户执行导入导致的,于是尝试用应用用户重新导入,问题依旧。然后又试着手动创建作业,结果log_user还是显示为SYS。经过一番排查,最终确认这是Oracle 19c的一个已知Bug(编号32249704)。
这个Bug的具体表现是:通过impdp导入作业时,log_user字段会被错误地设置为SYS用户,而priv_user和schema_user字段则保持正常。虽然看起来是个小问题,但在某些严格的权限控制环境下,可能会导致作业执行失败。
解决这个问题有两个方案:
- 安装Oracle官方提供的one-off补丁32249704
- 如果暂时无法打补丁,可以采取变通方案:
- 在导出前记录所有作业的原始log_user
- 导入后手动更新dba_jobs视图中的log_user字段
- 使用DBMS_IJOB包重建这些作业
我建议在生产环境中优先考虑打补丁的方案,虽然操作稍复杂,但能从根本上解决问题。临时方案虽然能应急,但后续维护会比较麻烦。
3. 物化视图刷新组创建失败问题
在导入过程中,遇到了一个关于物化视图刷新组的报错:"ORA-39083: Object type REFRESH_GROUP failed to create"和"ORA-01858: a non-numeric character was found where a numeric was expected"。这个错误信息看起来有点晦涩,我来拆解一下。
问题的核心在于刷新组的next_date参数。在源库中,这些刷新组对应的作业处于禁用状态(broken='Y'),导致next_date被设置为一个特殊值'4000-01-01'。在导入过程中,这个日期值被转换成了'00-JAN-01'的格式,而Oracle无法正确解析这个日期字符串。
要解决这个问题,我们需要修改导入脚本中的日期格式。具体操作如下:
- 首先找到报错的刷新组定义
- 将其中的next_date参数从'00-JAN-01'改为明确的日期格式:
to_date('4000-01-01 00:00:00','yyyy-mm-dd hh24:mi:ss') - 手动执行修改后的SQL脚本
这个问题的根本原因是Oracle在不同版本间对日期处理的差异。11g对某些特殊日期值比较宽容,而19c则要求更严格的格式。这也提醒我们,在跨版本迁移时要特别注意日期和时间相关的对象。
4. DBA_REFRESH视图中BROKEN字段显示异常
迁移完成后,在检查物化视图刷新状态时,发现DBA_REFRESH视图中的BROKEN字段显示为"?",而不是预期的"Y"或"N"。这个现象看起来很奇怪,值得深入分析。
通过查看DBA_REFRESH视图的定义,我发现BROKEN字段的值是通过解码job$表中的flag字段得到的。问题在于,在19c环境中,job$表竟然是空的!这就导致连接查询时返回null值,最终显示为"?"。
这个问题比较棘手,因为:
- Oracle官方文档中没有任何关于job$表为空的说明
- 目前没有公开的Bug与此相关
- 直接操作基表job$风险很大
经过多次尝试,我找到了一个可行的解决方案:绕过DBA_REFRESH视图,直接通过dba_jobs视图查询作业状态。虽然这不是最完美的方案,但至少能获取到需要的信息。
对于这个问题的长期解决方案,我建议:
- 关注Oracle官方的补丁更新
- 考虑使用Oracle Scheduler替代传统作业
- 如果需要严格监控,可以创建自定义视图来规避这个问题
5. 外键约束验证失败问题
在导入过程中,遇到了多个外键约束创建失败的报错:"ORA-02298: cannot validate - parent keys not found"。这个错误通常表示子表中存在父表没有的记录,导致外键约束无法建立。
排查这个问题的方法很明确:
- 检查父表和子表的数据完整性
- 确认约束状态是否正常
- 比较两边的数据一致性
在我的案例中,发现问题出在导出导入的时间差上。由于源库是生产环境,在导出父表和子表之间可能有数据变动,导致两边数据不一致。
解决这个问题的关键在于确保导出时所有相关表的数据处于一致状态。我采用了以下方法:
-- 首先查询当前SCN号 select to_char(current_scn) from v$database; -- 然后在expdp命令中使用FLASHBACK_SCN参数 expdp ... flashback_scn=<查询到的SCN>这种方法利用了Oracle的闪回查询特性,确保所有表都基于同一个时间点的数据快照导出。虽然导出过程会稍微慢一点,但能有效避免数据不一致的问题。
6. SYS对象权限丢失问题
最后一个问题是关于权限的。迁移完成后,发现目标库中某些对象的权限比源库少,特别是那些SYS用户拥有的对象。这看起来像是导入过程中丢失了部分授权。
经过仔细分析,发现这不是Bug,而是expdp的预期行为。Oracle的数据泵工具在设计时就有意不导出SYS对象的授权,主要是出于安全考虑。这个特性在官方文档中有说明,但很容易被忽略。
解决这个问题需要手动处理:
- 在源库生成所有必要的授权语句
- 在目标库导入前预先执行这些授权
- 然后再进行正式的数据导入
具体操作步骤如下:
-- 在源库生成授权脚本 connect / as sysdba spool grants_script.sql select 'grant ' || privilege || ' on ' ||'"'||table_name ||'"'|| ' to ' || grantee || ';' from dba_tab_privs where owner = 'SYS' and privilege not in ('READ', 'WRITE') and grantee in ('应用用户') order by 1; spool off这个问题的教训是:在规划迁移时,一定要全面考虑各种对象和权限,特别是那些系统对象的授权。最好在测试环境中先完整演练一遍,确保不会遗漏任何细节。
