尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Oracle 19c数据泵导入报ORA-39443?DBMS_DST时区升级实战

Oracle 19c数据泵导入报ORA-39443?DBMS_DST时区升级实战 事情发生在一次数据库迁移项目里。源库是一套打全了RU的Oracle 19c时区版本跟着补丁走到了42目标库是另一套老19cRU没跟上时区版本还停在32。expdp导出一切顺利impdp跑到一半控制台直接甩出一行报错ORA-39443: Export was done with a timezone (DST) version newer than the import database当时团队第一反应是数据泵怎么还管时区查了一圈才意识到只要涉及TIMESTAMP WITH TIME ZONETSTZ这类数据类型数据泵在导入前就会做一次“时区版本匹配”校验源端版本高于目标端就直接拦下来不给你慢慢查数据的机会。这篇文章就记录我从报错定位、版本核对、到用DBMS_DST把目标库时区文件从32升到42、最后重新导入成功的完整过程期间踩过的坑也一并写出来给做19c迁移和升级的朋友一个参考。1. 先弄懂报错TSTZ、时区版本和数据泵的关系1.1 我从哪条报错开始排查的ORA-39443这条错误原文写得很直白导出时的时区版本比导入端的新。也就是说dump文件里记录了一个“我是用什么时区版本导出的”元数据impdp读到之后会跟目标库当前生效的时区版本做比较发现源端42大于目标端32直接判定为“无法安全导入”。我一开始还以为是数据问题专门挑了报错行前面的几条数据去看结果发现普通字段都正常最后才反应过来是那几张带TSTZ字段的表触发了拦截。数据泵不是到真正写数据的时候才报错而是在校验阶段就拒绝往下走所以日志里往往就孤零零一行ORA-39443后面跟的SQL语句和对象信息都不明显。如果你遇到的是这条错误可以先不用怀疑dump损坏也不用怀疑磁盘空间大概率就是时区版本不匹配。把两边的时区版本查出来对比一下方向就清楚了。1.2 数据泵为什么非要管时区版本Oracle的时区文件timezone file里维护的并不是简单的“地区名偏移量”静态映射而是一整套夏令时规则每个地区的切换日期、切换前后的偏移量、历史上是否有过规则调整、未来几年怎么变全在这些文件里。比如某个地区在2019年之前实行夏令时2022年取消了。那么在32版本里2022年7月1日这个地区的UTC偏移可能是-6到了42版本同一天的偏移就变成了-7。TSTZ列里如果存的是地区名比如America/Chihuahua这种数据库必须结合当地时区规则才能算出正确的UTC时刻。如果源库用42版本的规则写入了数据目标库还在用32版本的规则解读同一个字符串“America/Chihuahua”在两套规则下可能对应两个完全不同的时刻。数据泵无法保证转换后的绝对时间一致所以干脆在导入前做版本校验版本对不上就报错。这不是Oracle故意卡你而是为了避免数据在迁移后出现“差一小时”这类很难发现的脏数据。1.3 什么业务场景最容易中招不是所有表都会触发这个校验只有使用下面两种数据类型的字段才会被时区文件影响TIMESTAMP WITH TIME ZONETSTZ存储里带地区名或偏移量例如2023-06-01 10:00:00 America/New_York。TIMESTAMP WITH LOCAL TIME ZONETSLTZ数据库统一按数据库时区存储查询时再转成会话时区。凡是订单表、流水表、日志表、异地考勤打卡表、航班时刻表这类带“发生时间且需要跨时区解释”的业务表如果设计时用了TSTZ或者TSLTZ迁移时就都躲不开时区版本这道坎。反过来说如果业务表只用DATE或TIMESTAMP不涉及地区名那这份dump从42版本导入32版本通常不会触发ORA-39443。还有一个很容易被忽略的场景源库升级过RU目标库长期没打RU。RU补丁会把新时区文件带进$ORACLE_HOME/oracore/zoneinfo目录但数据库字典里生效的版本并不会自动跳上去必须手动执行时区升级流程。所以很多源库的时区版本看起来“莫名其妙”就到了42目标库还老老实实停在32迁移一跑就暴露了。2. 升级前的体检版本确认与影响面评估2.1 三条SQL摸清时区现状动手升级之前先把源库、目标库、dump文件三方的时区版本关系搞清楚。我自己固定的第一件事就是查下面三条SQL-- 查看当前生效的时区文件版本 SELECT * FROM V$TIMEZONE_FILE; -- 通过数据库属性再确认一次 SELECT PROPERTY_NAME, VALUE FROM DATABASE_PROPERTIES WHERE PROPERTY_NAME LIKE DST%; -- 看历史升级记录如果之前升过 SELECT LOG_ID, LOG_TYPE, VERSION, STATUS FROM DBA_DST_UPGRADE_LOGS ORDER BY LOG_ID;V$TIMEZONE_FILE返回的是数据库当前正在使用的版本号DATABASE_PROPERTIES里的DST_PRIMARY_TT_VERSION也是一样的意思两个对上基本就没跑。DBA_DST_UPGRADE_LOGS能看到这台库历史上做过几次时区升级方便判断是不是以前升过又降过或者中间漏了步骤。查完我这边的情况是源库V$TIMEZONE_FILE显示42目标库显示32目标库DBA_DST_UPGRADE_LOGS里干干净净一条记录都没有说明从来没升过。方向非常明确必须给目标库升时区。2.2 判断该升级的是源库还是目标库很多人第一反应是“把源库降回32再导出”这方向完全反了。时区版本是不能随意回退的硬降会带来更大的数据一致性问题。正确思路永远是把版本低的一方升上去跟高的一方对齐。这也是Oracle官方的建议方向。源库既然已经是42那目标库就升到42反过来如果源库32、目标库42那你需要关注的是另一条错误ORA-39405意思是目标端版本高于源端dump版本数据泵同样不支持这种情况下反而要考虑把目标库“降”或者用其他手段规避。这里不展开因为实际生产环境里“源高目标低”才是最普遍的情况原因很简单源库往往是新部署或刚升级的目标库往往是老环境。还有一点要提醒判断版本的时候别只看操作系统的时区文件目录。$ORACLE_HOME/oracore/zoneinfo下面可能已经躺着timezlrg_42.dat这样的文件但数据库字典里生效的还是32。RU补丁只会把文件放到目录里需要你手动跑DBMS_DST或相关脚本让数据库真正把新版本“加载”进去。所以必须查V$TIMEZONE_FILE而不是ls文件目录。2.3 影响面评估哪些表会受影响时区升级不是无痕操作。版本从32升到42意味着所有带TSTZ/TSLTZ列的表如果有数据落在规则发生变化的时间区间里都需要重写。所以在真正动手之前一定要先评估影响面。影响面评估的准备工作Oracle也给了标准流程就是DBMS_DST里的prepare阶段。这个阶段会扫描全库找出所有包含TSTZ/TSLTZ列的表并估算每个表有多少行数据需要转换。这个数字决定了你的升级窗口要开多久也决定了要不要提前准备克隆环境做演练。这里分享一个我的习惯任何时区升级先克隆一套目标库在克隆上完整跑一遍prepare和upgrade记录耗时再回生产执行。时区升级虽然可以在线做但一旦出错、或者受影响行数比预期大好几个量级维护窗口就可能被拖爆。提前演练一遍心里有底得多。3. DBMS_DST升级实战从32到42的完整操作3.1 准备工作与注意事项正式操作前有几件事必须确认到位缺一个都可能翻车。第一备份。时区升级做完之后基本不能回退所以升级前必须有一份可用的备份。有条件就做冷备没条件至少要有RMAN的最近备份并且确认闪回恢复区空间足够。不要指望闪回数据库时区升级这种字典级变更不是简单一个FLASHBACK DATABASE能稳妥回到原点的。第二维护窗口与业务停机确认。虽然时区升级在文档里被描述为在线操作但prepare和upgrade期间强烈建议对带TSTZ/TSLTZ字段的表暂停DML。我自己做的习惯是直接开启受限会话把普通业务连接挡在外面只留DBA会话操作。ALTER SYSTEM ENABLE RESTRICTED SESSION; -- ... 升级完成后 ... ALTER SYSTEM DISABLE RESTRICTED SESSION;第三RAC环境只保留一个实例。时区升级要求实例之间不能互相干扰RAC下最好用SRVCTL把其他实例先停掉只留一个实例跑升级流程升完再启动其他实例。第四停掉DBMS_SCHEDULER的Job。尤其是那些可能往带TSTZ/TSLTZ字段的表里写数据的定时任务升级期间必须禁用不然一边升级一边写数据很容易造成转换遗漏或者报ORA-01422这种令人抓狂的错误。3.2 Prepare阶段扫描受影响对象准备工作做完进入prepare阶段。这个阶段分三步建错误表、开始prepare、找出受影响表。-- 1. 建错误表用于记录转换失败的行 BEGIN DBMS_DST.CREATE_ERROR_TABLE(SYSTEM, TSTZ_ERR); END; / -- 2. 开始prepare BEGIN DBMS_DST.BEGIN_PREPARE; END; / -- 3. 扫描所有带TSTZ/TSLTZ列的表结果存入SYSTEM.AFFECTED_TABS BEGIN DBMS_DST.FIND_AFFECTED_TABLES( affected_tables SYSTEM.AFFECTED_TABS, log_errors TRUE, log_errors_table SYSTEM.TSTZ_ERR ); END; /FIND_AFFECTED_TABLES这一步是异步任务提交后不会立刻返回结果需要轮询DBA_DST_AFFECTED_TABLES视图来观察进度-- 轮询受影响表的扫描状态 SELECT MASTER_STATUS, CLIENT_STATUS, COUNT(*) FROM DBA_DST_AFFECTED_TABLES GROUP BY MASTER_STATUS, CLIENT_STATUS; -- 查看具体表和预估行数 SELECT OWNER, TABLE_NAME, COLUMN_NAME, ROW_COUNT FROM DBA_DST_AFFECTED_TABLES ORDER BY ROW_COUNT DESC;我这次扫描的结果是两套业务库里大概有三十多张表带TSTZ/TSLTZ字段真正有数据需要转换的十几张最大的一张流水表有上千万行。看到这个数字我就知道升级后的受影响表转换阶段得单独给它留时间不能指望几分钟跑完。当DBA_DST_AFFECTED_TABLES里所有记录的MASTER_STATUS都变成成状态、SYSTEM.AFFECTED_TABS里的数据也齐了之后就可以结束prepare阶段BEGIN DBMS_DST.END_PREPARE; END; /prepare阶段结束之后受影响表清单会被“冻结”下来后续升级和转换都基于这份清单所以这个阶段一定要扫描完整别急着结束。3.3 Upgrade阶段时区版本正式切换prepare完成接下来就是把数据库的时区版本从32升到42的核心阶段。这里涉及到三个连续动作BEGIN_UPGRADE、UPGRADE_DATABASE、END_UPGRADE。-- 1. 开始升级指定目标版本42 BEGIN DBMS_DST.BEGIN_UPGRADE(42); END; / -- 2. 提交后台升级任务 DECLARE v_job_name VARCHAR2(100) : TSTZ_UPGRADE_42; BEGIN DBMS_DST.UPGRADE_DATABASE(v_job_name); END; / -- 3. 检查升级日志 SELECT LOG_ID, LOG_TYPE, VERSION, STATUS, ERROR_COUNT FROM DBA_DST_UPGRADE_LOGS ORDER BY LOG_ID; -- 4. 看有没有报错记录 SELECT * FROM DBA_DST_ERRORS ORDER BY LOG_ID;UPGRADE_DATABASE同样是异步任务提交后会在后台跑。日志视图里的状态字段一般会经历WAITING、WORKING最后变成SUCCESS或者FAILED。如果是FAILED一定先去DBA_DST_ERRORS里看具体错误原因别急着重跑。这里说一下我踩过的一个坑第一次在测试环境跑的时候BEGIN_UPGRADE完了没等UPGRADE_DATABASE的日志状态变成SUCCESS就直接执行了END_UPGRADE结果状态错乱后面再查V$TIMEZONE_FILE发现版本没变最后只能重新走一遍prepare。后来养成了习惯END_UPGRADE之前必须确认DBA_DST_UPGRADE_LOGS里最新那条记录的STATUS是SUCCESS否则绝不往下走。升级核心任务跑完之后再执行END_UPGRADEBEGIN DBMS_DST.END_UPGRADE; END; /到这里数据库字典里生效的时区版本就已经是42了。可以用下面的SQL验证一下SELECT * FROM V$TIMEZONE_FILE; SELECT PROPERTY_NAME, VALUE FROM DATABASE_PROPERTIES WHERE PROPERTY_NAME DST_PRIMARY_TT_VERSION;如果显示版本已经是42恭喜最关键的字典升级完成了。但先别高兴太早还有一步更耗时的操作要做。3.4 升级受影响表数据时区版本切到42之后旧规则和旧数据不会自动转换。那些在prepare阶段被扫出来的受影响表还需要用UPGRADE_AFFECTED_TABLES把每一行TSTZ/TSLTZ数据按新规则重写一遍确保同一时刻在物理上没有变化。BEGIN DBMS_DST.UPGRADE_AFFECTED_TABLES( affected_tables SYSTEM.AFFECTED_TABS, log_errors TRUE, log_errors_table SYSTEM.TSTZ_ERR ); END; /这个过程同样异步执行也需要观察DBA_DST_UPGRADE_LOGS和DBA_DST_ERRORS。受影响行数大的表这个阶段可能持续几十分钟甚至几小时。我这次最大的那张流水表就跑了四十多分钟。转换期间如果某些行因为规则无法映射而报错错误信息会写进我们一开始建的SYSTEM.TSTZ_ERR错误表。处理思路一般是先看是不是数据本身就有问题比如存了一个不存在的地区名修复源数据如果是偶发错误可以在确认错误表里没有新的报错后重新执行UPGRADE_AFFECTED_TABLES让遗漏的行继续转换。转换完成后建议抽几张核心表做前后对比验证。一个简单有效的验证方法是取转换前备份出来的数据跟转换后的数据做一次抽样对比确认“物理时刻”没变。比如转换前是2022-07-01 08:00:00 America/X规则变化后转换完可能变成2022-07-01 07:00:00 America/X但它的UTC绝对时间应该完全一致。3.5 回到impdp重导数据并验证目标库时区版本升到42、受影响表也转换完成之后回到最初的报错现场。因为dump文件里记录的源端时区版本就是42目标库现在也是42版本匹配校验可以顺利通过直接重新执行之前失败的impdp命令就行不需要重新导出一遍。重导之前我再三强调了检查目标库状态确认V$TIMEZONE_FILE版本是42。确认受限会话已经关闭业务连接能正常进来。确认之前禁用的Job已经恢复。然后重新跑impdp这次日志里不再出现ORA-39443数据正常落库。导入完成后我抽了那张之前报错最频繁的流水表对比源库和目标库的TSTZ字段值发现完全一致心里的石头才落地。这里有一个很容易忽略的细节如果你导入的是PDB时区版本检查是按容器来的CDB root升了不代表PDB的时区版本也跟着升需要进到每个PDB里单独确认和执行升级。我这次遇到的是非容器库所以流程相对简单。如果你在公司里用的是多租户架构记得把这一步加进排查清单。4. 常见问题与排查技巧实录4.1 常见报错速查表时区升级和TSTZ相关报错我在项目里整理过一个速查表遇到问题先对照一下能省不少排查时间报错代码典型场景处理方向ORA-39443源库时区版本高于目标库升级目标库时区版本与源库对齐ORA-39405目标库时区版本高于源库dump确认版本关系不能简单盲升需评估目标库版本差异ORA-01882dump里包含目标库时区文件里没有的地区名升级目标库时区版本或者把该列转为偏移量格式ORA-30078会话时区设置成目标库不认识的地区调整会话时区设置或升级时区版本ORA-01899TSTZ值在转换时无法解析检查错误表针对失败行单独修复ORA-39443和ORA-39405就是时区升级问题的“门神”一左一右拦着版本不匹配的导入操作。遇到这两个停下来把源库、目标库、dump三方的时区版本摸一遍通常五分钟就能定位。4.2 升级过程中遇到的几个“坑”第一直接替换$ORACLE_HOME/oracore/zoneinfo下的DAT文件是无效的。有人想走捷径把源库目录里的timezlrg_42.dat拷到目标库以为重启一下就生效结果数据库压根不认。时区文件必须通过DBMS_DST这类正规流程加载进数据库字典光替换操作系统文件等于白干。第二所有实例必须同步配合。RAC环境下如果你在1号实例跑升级2号实例还开着并且有会话正在写TSTZ数据升级过程非常容易在中途报内部错误。最稳妥的做法就是只保留一个实例其他实例先停掉升完再启动。第三升级期间别对受影响表做DDL。哪怕是加一个索引也可能导致受影响表清单前后不一致。我在测试环境就遇到过升级过程中有人给一张大表加了分区结果UPGRADE_AFFECTED_TABLES阶段直接跳过那张表最后核对数据才发现漏转了。第四操作系统时间和实际UTC之间别搞混。时区版本升级只影响数据库里的时区规则和TSTZ/TSLTZ数据不影响操作系统时间也不影响SYSDATE。别升完之后发现系统时间没变就以为升级失败了这是正常的。4.3 一步到位还是逐级升实操层面的选择从一个时区版本升到另一个时区版本在19c里可以直接指定目标版本比如从32一步升到42前提是$ORACLE_HOME/oracore/zoneinfo目录下已经有对应的目标版本文件。我这次因为目标库RU补丁已经带上42文件所以直接一步到位没有逐级升实测没有问题。但如果你遇到“目标版本文件不在目录里”的情况就不能硬升得先把RU补丁打到对应版本或者考虑分两步升。判断依据很简单V$TIMEZONE_FILE里能看到当前版本如果目标版本对应的DAT文件在操作系统目录里存在一般就能直接升。升级前先用前面说的三条SQL把版本关系确认清楚别盲目指定一个版本号。另外如果你不想用DBMS_DST一步步操作Oracle还提供了utltz.sql和utltz_u.sql这类脚本可以快速完成升级。脚本方式适合时间紧、受影响表少的场景我做小库升级时会用但大库还是推荐走DBMS_DST因为它分prepare和upgrade阶段影响面、错误记录都更清晰好跟踪也好看门。我个人在实际操作中的体会是时区版本升级这件事真正难的不是执行那几条DBMS_DST调用而是升级前的影响面评估和升级后的数据核对。再遇到过几次之后我现在做任何19c迁移项目第一步就把源库和目标库的时区版本查出来写进方案里版本不一致直接列入风险项提前规划升级窗口。这样等到impdp报ORA-39443再回头折腾已经是不该发生的意外了。最后再分享一个小技巧升级前把V$TIMEZONE_FILE的查询结果、受影响表清单、升级日志都留一份截图或文本存到项目文档里后面不管是复盘还是审计都有据可查省得每次都要重新翻库。
返回列表