ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Oracle升级再遇ORA-20001?详解XDB XML inventory的定位与修复

Oracle升级再遇ORA-20001?详解XDB XML inventory的定位与修复 升级 Oracle 时再次撞上 ORA-20001这次盯紧 XDB 的 XML inventory前阵子帮客户做 12.1 到 19c 的数据库升级中途在 alert 日志里看到了那个让我非常熟悉又头疼的错误ORA-20001: Latest xml inventory is not loaded into table。升级脚本在 XDB 阶段停下来后续所有组件全部卡住。当时第一反应是“坏了又要翻内部表”。排查一圈后发现这个问题虽然报错信息比较隐晦但只要理解它背后的机制解决路径其实很固定。这篇就把我从报错到定位再到修复的完整过程拆开讲包括 XML inventory 到底是张什么表、为什么升级会依赖它、有哪些能直接抄的 SQL 和脚本操作。这个错误的主要坑在于它不在你日常跑的 SQL 里出现而是在数据库升级、组件重装、或者是 XDB 相关脚本执行的场景里冷不丁冒出来。很多人第一次看到“Latest xml inventory is not loaded into table”直接愣住因为数据库里根本没有一个能直观对应上的业务表。实际上它和 XDB 内部的对象清单机制有关属于 Oracle XML DB 组件升级检查的一部分。1. 错误场景还原什么时候最容易踩到它1.1 最常见的触发路径是升级脚本跑到 XDB 阶段我遇到的绝大多数 ORA-20001都发生在 Oracle 数据库大版本升级过程中。比如 11.2 升到 12.212.2 升到 19c或者 19c 升到 21c 这类跨版本升级。整个升级过程由 catupgrd.sql 或者 catctl.pl 调度它会按组件顺序逐个执行升级脚本。XDB 组件对应的升级脚本一般叫 catxdb.sql这个脚本内部会初始化、校验一系列 XML 对象和元数据。在这个阶段如果出了问题告警日志和 catupgrd 的输出里就会出现类似下面的信息ORA-20001: Latest xml inventory is not loaded into table ORA-06512: at SYS.DBMS_SYS_ERROR, line 25 ORA-06512: at SYS.DBMS_XMLDB_UPGRADE, line ... ORA-06512: at line 1除了升级过程还有两个场景也很容易复现这个错误。一个是在已经升级过但中途失败的库上手动补跑 catxdb.sql另一个是在多租户架构下某个 PDB 的 XDB 组件状态异常你试图单独对某个 PDB 做 XML DB 的重新配置时。总之这个错误基本都集中在 XDB 升级相关路径上和你的应用 SQL 通常没有关系。1.2 ORA-20001 是“用户自定义错误”区间但背后是 Oracle 内置校验Oracle 的错误码范围里ORA-20000 到 ORA-20999 是预留给用户通过 RAISE_APPLICATION_ERROR 来自定义错误使用的。所以刚接触这个报错的 DBA 会困惑我没写任何存储过程怎么会抛一个用户自定义错误实际上这是 Oracle 自己的内部包在用这个机制。升级代码在运行到某个校验逻辑时发现前置条件不满足于是调用了 RAISE_APPLICATION_ERROR把真正的失败原因抛出来。翻译一下这句英文最新的 XML 库存清单没有加载到表中。换句话说升级程序在启动时要求某个内部表里必须存在一条状态为“最新”的 XML inventory 记录但实际查不到因此直接中断。这个校验存在的目的是为了防止在不完整的元数据基础上继续做对象迁移避免把 XDB 组件搞成一个坏状态。2. XML inventory 到底是什么东西为什么升级离不开它2.1 它保存的是 XDB 组件升级时需要处理的 XML 对象清单我在这里说的 XML inventory并不是业务系统里的库存表而是 Oracle XML DB 内部维护的一张清单表。XDB 组件本身管理了很多 XML 相关的元数据对象比如已注册的 XML Schema、XMLType 表、XDB 的 ACL、路径索引、参数表等。当数据库升级时这些对象可能因为版本变化需要批量调整、迁移或重新编译Oracle 需要先知道这个数据库里到底有哪些 XML 对象以及每个对象当前处于什么版本状态。于是在 XDB 安装或升级过程中Oracle 会把这些待处理对象的信息做成一份清单写进内部表并给这份清单打上版本号或批次号。升级校验逻辑检查这份清单是否存在、是否是最新版本。如果清单缺失、为空、版本不对或者没来得及加载就会抛 ORA-20001。可以用一个生活化的类比来理解整个 XDB 升级就是一个搬家过程XML inventory 就是搬家工人手里的物品清单。搬家之前工人必须先把清单读完知道哪个箱子该去哪间房。如果清单没装进他的平板电脑你让他直接开工他肯定罢工。ORA-20001 就是这个罢工信号。2.2 “Latest” 这个词点出了关键设计为什么报错信息里要强调“latest”因为 XDB 升级不是一次性把所有 XML 对象全部处理完的。Oracle 内部可能保存了多份清单快照代表了不同升级阶段或不同版本批次的状态。程序只认最新那一份。如果某次升级因为断电、磁盘满、人为中止而中途失败清单表的写入可能停留在旧版本或者是根本没写入下一次再跑升级时就会因为找不到最新清单而报错。这解释了为什么在很多实际案例里这个错误会出现在“第一次升级失败修复后重跑升级”的场景。第一次失败时XDB 内部的一部分元数据已经改了但清单没有更新到位数据库处于一个半新不旧的状态这时候最容易触发。2.3 怎么快速找到这张表和它的状态平时很难注意到这张清单表因为它属于 Oracle 内部实现。但在排查时可以先去 XDB 相关的模式里查一下带 INVENTORY 关键字的对象再结合 dba_registry 看组件状态。我常用的是下面这些查询。-- 1. 看 XDB 组件整体状态 SELECT comp_id, version, status FROM dba_registry WHERE comp_id XDB; -- 2. 查看 XDB 组件的升级历史 SELECT comp_id, version, action, action_time FROM registry$history WHERE comp_id XDB ORDER BY action_time; -- 3. 查找可能的清单表 SELECT owner, table_name, tablespace_name FROM dba_tables WHERE owner IN (SYS, XDB) AND table_name LIKE %INVENTORY%; -- 4. 检查 XDB 模式下处于失效状态的对象数量 SELECT status, count(*) FROM dba_objects WHERE owner XDB GROUP BY status;这些查询并不能直接告诉你“清单是不是最新”但能帮助你判断 XDB 组件当前处于什么状态。如果 dba_registry 里 XDB 的 status 是 UPGRADED 或者 VALID而且 registry$history 里能看到最近一次成功动作说明组件本身没有大问题。如果 XDB 状态是 INVALID 或者 BEING UPGRADED那大概率就是升级中途失败清单没有正确落表。3. 定位排查别上来就重装组件先按顺序做三件事3.1 明确当前库属于哪种异常状态遇到 ORA-20001不要急着去重建 XDB 或者删表。先判断数据库到底处在哪个阶段。我一般把情况分成三类第一类是升级脚本确实在当前会话里抛错但没有做过任何额外干预这种情况往往是因为升级环境不干净比如之前 XDB 升级残留了半成品。第二类是升级失败后手动跑了某些 XDB 包导致内部版本信息错乱。第三类是数据库本身没有在升级但你手动执行 catxdb.sql 或者相关 XDB 初始化脚本时触发了。区分这三类的办法很简单看 alert 日志和 catupgrd 日志的上下文。如果错误紧跟在某个汇编译阶段后面优先级先考虑残留半成品如果是在你手动操作后才出现优先考虑版本错乱如果是独立执行 XDB 脚本时出现大概率是脚本补齐逻辑与当前字典信息不匹配。3.2 利用 trace 找出具体抛出点ORA-20001 真正的抛出者往往不是升级脚本本身而是内部包。要想知道具体是哪一段校验代码、在检查哪张表之前抛出来的可以开 10046 事件追踪一下。ALTER SESSION SET events 10046 trace name context forever, level 12; -- 然后重新执行报错的升级脚本或存储过程调用 ALTER SESSION SET events 10046 trace name context off;打开 trace 后会在输出文件里看到完整的内部包调用栈包括 DBMS_XMLDB_UPGRADE 这一类内部单元函数。这个栈能帮你确认错误确实来自 XDB 的升级校验逻辑而不是别的地方混进来的。不过对大多数情况来说这一步不是必须的。错误信息里已经明确告诉你是“xml inventory not loaded”加上错误栈包含 DBMS_XMLDB 相关的调用基本就可以把排查范围锁定在 XDB 组件了。3.3 记录报错时的其他连带错误有时候 ORA-20001 不是孤立的在它之前或之后还会跟着几个别的错误。最常见的是 ORA-01422实际返回的行数超过请求的行数或者 ORA-31001XDB 相关的 XML 操作错误。这些连带错误往往是在清单缺失后升级代码尝试读取某条不存在的记录时产生的次要异常。所以在排查时眼睛不要只盯着 ORA-20001 一行。把 alert 日志中前后 100 行都扒出来看一遍尤其是错误出现之前有没有 ORA-00600、ORA-07445或者有没有磁盘空间不足、数据文件扩展失败之类的底层警告。这些前置错误有时候才是真正的元凶ORA-20001 只是它们引发的连锁反应。4. 修复实操三种方案按风险从低到高排列4.1 重新执行 XDB 升级脚本是最常见且稳妥的做法在确认不是磁盘满、权限错误等外部因素之后我建议优先尝试重新执行 XDB 升级脚本。这个方法的目标是让 Oracle 自己重新完成清单加载和后续的对象升级动作。操作方式比较直接。在单实例非 CDB 环境里用 sysdba 登录后执行sqlplus / as sysdba SQL $ORACLE_HOME/rdbms/admin/catxdb.sql执行时注意观察脚本输出重点关注有没有“XDB upgrade completed”一类的成功提示。脚本跑完后再回到 dba_registry 确认状态SELECT comp_id, version, status FROM dba_registry WHERE comp_id XDB;如果状态还是不对可以再补跑一遍 utlrp.sql 编译失效对象SQL $ORACLE_HOME/rdbms/admin/utlrp.sql这个过程相当于把 XDB 升级流程重新走一遍。很多 ORA-20001 的案例在重新执行 catxdb.sql 后就能正常完成原因在于上一次升级虽然失败了但基础结构没有被破坏脚本具备幂等恢复能力第二次执行会检测到状态并补齐缺失的清单。如果是多租户架构需要注意执行位置。通常只需要在升级种子数据库和用户 PDB 里分别确认 XDB 状态。执行时先确保目标容器已经打开然后进入对应 PDB 再跑脚本sqlplus / as sysdba ALTER PLUGGABLE DATABASE pdb_name OPEN; ALTER SESSION SET CONTAINERpdb_name; $ORACLE_HOME/rdbms/admin/catxdb.sql4.2 通过内部例程重新初始化清单数据如果重跑 catxdb.sql 仍然报同样的错误说明 XDB 的内部状态比预想中更混乱清单记录可能是缺失或者损坏而不是简单的“没跑完”。这时候再一味跑脚本也没有意义因为它可能在校验阶段就反复地提前退出。这种情况下我一般会先尝试通过 Oracle 提供的 XDB 重装初始化脚本把基础对象重建一遍然后再执行升级脚本。比较常用的是 catqm.sql它会重新创建 XDB 的基础 schema、存储对象和一部分元数据相当于把 XDB 的存储骨架重新打一遍底子。具体操作时需要带上与当前字符集相关的参数。在单实例环境里执行格式大概是sqlplus / as sysdba $ORACLE_HOME/rdbms/admin/catqm.sql tablespace_name temp_tablespace_name schema_name这里三个参数分别是 XDB 使用的数据表空间、临时表空间、以及你希望存放 XML Schema 的用户名。如果不确定当前库历史上用的是哪个表空间和哪个 schema先查 dba_tablespaces 和 dba_users 确认再用原来的名字执行避免造成新的不一致。执行完 catqm.sql 之后再回到 catxdb.sql 跑一遍升级把版本推进到目标版本。这个方法比单纯重跑 catxdb.sql 的介入力度大但仍然属于可控范围不会清掉业务数据只是重建 XDB 组件自己的基础对象。这里需要特别提示网上有一些帖子建议手工往清单表里 insert 缺失的行。我强烈不建议这么做。清单表里的字段包含对象类型、版本批次、内部 ID 等大量信息手工猜出来的行几乎不可能和真正需要升级的对象集合完全匹配。一旦插入错误数据会比缺数据更麻烦后续升级可能在任何诡异的位置报错。4.3 完全重建 XDB 组件作为最后手段如果上面两步都无效或者你检查后发现 XDB 模式里的对象大量失效、内部结构已经明显不完整那可以考虑走完全重建 XDB 组件的路线。这个方案风险最高但对某些严重损坏的库来说反而可能是恢复时间最短的方案。重建的大致思路是先移除现有的 XDB 组件对象然后重新执行 XDB 初始化脚本最后再执行升级脚本。在操作之前必须把数据库做一次完整的备份至少要做一个可恢复的 dump因为一旦过程中出现问题你需要一个可靠的还原点。重建时不能在实例正常运行状态下直接 drop 相关用户或模式需要先关闭数据库以受限模式启动再执行相应的重建步骤。由于每套环境的版本和打补丁情况不一样这里不给出具体的命令序列因为你照着别人环境抄很可能翻车。我建议在真正动手前先去看当前版本对应的官方安装指南里关于 XDB 配置的章节找到适用于目标版本的脚本清单。判断是否必须走完全重建有一个比较实用的思路走完 4.1 和 4.2 之后如果 dba_registry 中的 XDB 状态依然不是 VALID而且 XDB schema 下失效对象数量持续增多同时你确认没有任何业务 XMLType 表或 XML schema 需要保留这时候重建才值得赌一把。如果库里有大量业务 XMLType 数据我更愿意选择从备份恢复而不是花几个小时去重建组件后冒兼容性风险。5. 常见问题排查速查表和避坑经验5.1 升级阶段常见的连带症状对照下面这张表是我在实际处理中总结的高频组合对快速判断很有帮助。伴随现象可能原因处理优先级alert 日志出现 ORA-07445 或 ORA-00600底层内存或并发问题先解决底层问题再管 XDB升级日志显示 xml inventory 相关表空间扩容失败表空间不足导致清单写不进去扩容后重跑升级脚本dba_registry 中 XDB 状态为 BEING UPGRADED上次升级非正常中断直接重跑 catxdb.sqlXDB 模式下大量对象 INVALID脚本中断在编译阶段执行 utlrp.sql 编译后再校验同时出现 ORA-01422清单查出来多条记录或版本不一致检查 registry$history必要时重建 XDB5.2 升级前应该做的几个预防动作我后来在对接的所有升级项目里都把 XDB 相关的检查列入升级前 checklist防止中途再来一次 ORA-20001。升级前先确认 XDB 当前处于正常可用状态也就是 dba_registry 里 status 是 VALID。升级前检查默认表空间和临时表空间剩余空间至少预留出 XDB 对象重新编译所需的量级。升级前跑一遍 utlrp把已存在的失效对象处理干净避免升级过程中叠加编译错误。升级过程中不要并发执行其他 DDL 或者物理备份操作给 catxdb.sql 留一个干净的环境。5.3 止损经验三秒钟判断该不该继续往下跑升级脚本报 ORA-20001 的那一刻你必须迅速判断是继续跑还是先停。我的习惯是看错误出现的位置。如果错误出现在 XDB 升级脚本的早期校验阶段说明除了清单缺失之外其他还未开始大幅变动这时候停掉升级修复清单然后再重跑是最划算的。如果错误出现在 XDB 脚本的中后期此时一部分 XML 对象已经本版更新或变更过了最好先记录当前状态不要盲目从头重跑整个升级否则反而可能因为重复执行部分逻辑造成更多不一致。判断位置的一个粗糙办法是看 alert 日志的时间线。从 catxdb.sql 开始执行到报错之间如果只有几秒钟说明处于早期校验如果已经跑了几分钟甚至更久那多半是中后期了。这种场景下我建议优先清理残留再重跑而不是强制继续。根据我个人的处理经验最有效的组合拳是先确认没有底层错误然后重跑 catxdb.sql 一次如果仍失败就补跑 catqm.sql 再重跑一次。绝大多数 ORA-20001 都会在这一步解决。真正需要走到完全重建的案例很少而且多半是之前有人手动操作过内部表或者删过 XDB 相关对象才把局面推向不可收拾。所以当你面对这个错误时保持步骤秩序比什么都重要不要因为急于恢复而跳过流程。
返回列表