前几天某客户紧急求助我们,其 Oracle 数据库由于重启之后无法正常启动。最后通过数据库全备进行了一天一夜的恢复,最后仍然无法正常打开数据库。
alter database open 时检查发现数据库报错 ORA-16703。
从用户提供的信息来看,确实是在 open resetlogs 的时候出现的错误。
那么这个错误意味着什么呢?
其实第一眼看到这个错误时,我们就大概清楚这是 Oracle 的数据字典出问题了, 而且这通常是 Oracle tab$。
接到这个 case,我开始感觉是非常的奇怪。为什么客户利用 Oracle rman 全备 + 归档进行恢复,然后 open 的时候居然报数据字典有问题呢?感觉有点匪夷所思。
首先进行验证什么信息?
很简单,确认 tab$ 是否真的有问题。
这里其实有 2 种方法:
实际上这里我首先通过 10046 event 跟踪了一下,发现确实如此,为了更加确认,我将 system 文件 cp 到文件系统,通过 ODU 抽取了 tab$ 的数据,发现居然是 0 行。
这说明什么呢? 说明 tab$ 的数据被人清空了?
我相信只有这一种解释了。发现了问题,没什么用呀。我们需要尽快帮用户恢复生产库,恢复业务。这是关键。
由于客户之前的环境已经被人 resetlogs 了多次,因此不再适合继续恢复了;首先我们通过全备进行了一次恢复,然后尝试打开了数据库。确实非常顺利,但是遗憾的,不到 1 分钟数据库就宕机了,然后再次启动就是报同样的错误 ORA-16073。
还好我此时多了一个心眼,open 之前我先备份了 system 文件;此时再次通过 odu 抽取 tab$ 的数据进行对比发现;open 之前数据是存在的;open 之后数据库宕机,然后再次查看 tab$ 数据为 0。
很明显,问题出在 open 之后的一个极其短暂的时内。通常这种破坏操作都是通过存储过程或者 trigger 等来进行;因此我尝试通过 odu 抽取了 obj$ 的信息。发现该数据库在 2017 年 9 月 2 号凌晨创建了几个特殊对象,猜测就是这个东西在捣鬼了。
这几个 dbms_support 的对象明显是有问题的。看来这个问题在 1 个月前就潜伏了,只是用户没有发觉而已。结合 alert log 分析判断 9 月 2 号客户应该是进行过数据库升级操作,后面跟客户确认也确实如此。
难道问题出在升级的环境?
问过当时升级的工程师,整个过程没有任何问题,只是简单的将数据库从 11.2.0.3 升级到 11.2.0.4。 想到这里,问怀疑问题可能出现在 Oracle 软件安装包上。搜了一下 Mos 发现这个 dbms_support 对象在安装升级过程运行?/rdbms/admin/prvtsupp.plb 脚本产生的内容。
这个脚本是否被动过手脚?
strings 看了一下脚本内容,发现确实有问题。如下是被恶意注入后的脚本:
如下是我的 11.2.0.4 环境的正常脚本内容:
我们可以清楚的看到,前面的大部分内容被篡改了。对于这个恶意攻击脚本,我尝试进行解密,但是没有成功。
后面研究了一下,稍微修改一下脚本,即可顺利解密,解密出来的代码如下所示:
注意!注意!禁止拿去搞破坏性动作!一概不负责!
对于 Oracle 自带的这个正常的 prvtsupp.plb 的脚本,可以轻易解密:
如何处理呢?
这就不太难了。我尝试用提前 cp 备份的 system 文件进行替换,然后推进 scn 顺利打开了数据库,打开之后,我离开进行了如下的操作。
这里需要注意的是,对于这个隐含参数,建议 open 之前打开,可以起到类似将数据库在 upgrade 模式下操作的效果 (drop 操作要够快,最好是命令与 open 操作一起执行)。
事情到这里还没结束,可能是我操作不够快还是怎么着。最后 dbmonitorp 这个私活无法 drop,会一直挂起。不过 trigger 被 drop 了,那么只是问题不会再次触发了,除非手工调用这个存储过程。
最后客户测试应用时,发现有将近 10 个表有问题,报错 ORA-30732 错误。这个错误本身来讲不难处理,重建对象即可。问题是当我尝试重建 table 时,发现 session 直接挂起。通过 10046 event 跟踪 session 发现一直时 row cache lock,如下所示:
这确实有些怪异了。通过上面毒 cahce id=12 我们可以进一步定位到是数据库的约束可能有问题,如下:
约束有问题?
各位不要惊讶,这里完全有可能,因为数据库是强制 open 的,可能有不一致的情况出现。为了进行验证,我创建一个不带约束的 table 发现确实 ok,带上 not null 的约束就 hang 住。
最后在自己的 11.2.0.4 的数据库进行了简单测试发现:
1、create table(带约束的情况下)会如下几个基表的操作,但是与约束有关系的,其实就 con$,cdef$:
2、创建约束时,Oracle 会以_next_constraint 的 con# 值为当前所能搞创建成功的约束的 con#; 该值必须比 con$.max(con#) 要大。 其实只要大于即可。
根据类似的思路我对客户这套数据库进行了简单检查,发现数据字典确实有问题,如下:
con$ 的记录均包含了 cdef$。因此这里我们不需要太关注 cdef$。
con$:
由于其 i_con2 这个唯一索引中最大值是 144216,因此我们需要将表中 con# >144216 的记录全部标记为删除
cdef$:
由于 cdef$ 中 con# 最大记录是 144193,因此需要将其索引 I_CDEF1 中的 con# > 144193 的键值全部标记为删除。
这里我们通过 bbed 修复了上述对应的一些 data block 和 Index Block,但是创建 table 时发现还是 hang 住。难道哪个地方没有修改对吗?
由于我的测试环境的情况是需要_next_constraint 能够正常工作,按理说都是 ok 的。
那么问题出现在什么的地方呢?
这里我们先尝试来查看一条正常的记录,例如 con#=144193:
大家可以看到,dba 地址和行号都应该是对应起来的 (这里我没有显示行号).
我们再来看看异常的这条数据:
很明显,rdba 地址都不匹配呀(注意:前面基于 rowid 的查询, 不加 hint 的情况下,走的是 Index 扫描)。以为这里将 rdba 修改为 file 1 block 289 就 ok 了,发现还是不行。为什么呢? 这里给自己挖了一个坑。后面再次查询发现行号其实也不匹配,正常应该对应第 12 行,实际这里错误的对应到 18 行了。如下是该数据块的 dump 情况:
看来这确实是我们需要的这条数据,非常珍贵的一条数据呀。当最后将 index block 中的行号也修改为一致时,再次测试发现就 ok 了。不过我这里还是直接将该条记录 delete 条了,然后插入一条新的记录 (有些人会说,这里如果不修改能否 delete 呢?其实不行的,delete 会报错):
整个恢复过程其实要比这个复杂一些,省略了一些步骤,不过基本上差不了太多。大家将就看喏~~
原文发布时间为:2017-12-27
来源: https://yq.aliyun.com/articles/319580