TRUNCATE恢复-bbed

本文的truncate恢复只针对于堆表(非lob)进行了测试,其实对于分区表和lob段的恢复原理是一样的。
根据之前对truncate原理的分析,truncate是不能通过闪回查询或者logminer的方式来恢复的,因为truncate操作不会对数据块进行任何操作。那么truncate应该如何恢复呢?下面列出几种常见的方法可供参考。

  • 数据库闪回(要求flashback database开启,并且必要的闪回日志和归档日志不能丢失,因为闪回数据库不仅仅需要应用闪回日志,归档日志也是需要的)
  • 异机恢复(要求有可用的备份以及必要的归档日志)
  • TSPITR(要求有可用的备份以及必要的归档日志)

其中数据库闪回和TSPITR对数据库影响较大。假如数据库flashback database没有开启,并且无备份的情况下该如何恢复呢?本文介绍两种不常见的方法,重点介绍第二种修复元数据方式:

  • odu(要求数据不被覆盖,如果数据被覆盖也可以最大程度的恢复数据)
  • 通过修复元数据来实现恢复truncate(要求有truncate操作时的redo信息,并且数据不被覆盖,如果数据被覆盖也可以最大程度的恢复数据)

odu的方式:
odu是熊爷开发的一款专业而且强大的oracle恢复工具,适用于所有场景下的恢复,具体查看http://www.oracleodu.com/cn/,这里简单介绍一下odu恢复truncate的原理。odu恢复truncate的原理是通过scan数据文件生成一个ext.odu的文件,该文件是按照表的dataobj#扫描出具体的extent信息,然后通过ext.odu可以导出需要恢复的表的数据,最终再导入到数据库中。

修复元数据的方式:
根据之前对truncate原理的分析,truncate的实质是在不修改数据块的情况下,通过修改segment header的data_object_id,hwm,extent map,aux map等信息来实现清空表的目的,其中还涉及数据字典基表以及L1、L2位图块的修改,那么对于通过修改元数据的方式去恢复,首先需要确认哪些元数据块和数据字典是需要恢复的。通过10046的跟踪发现(可以去验证一下,需要flush shared pool和buffer cache),全表扫描查询或者是通过rowid去查询一定会访问segment header,但是不会去访问任何L1、L2位图块的,访问的数据字典基表包括user$、obj$、tab$、tab_stats$、ts$、seg$、ind$、ind_stats$、col$、objauth$、cdef$、histgrm$、hist_head$,这里重点关注之前通过10046跟踪truncate操作有更改的基表obj$、tab$、seg$、tab_stats$(统计信息不用管),其中seg$经过测试只要block#,file#,ts#不被更改就无需理会,而truncate操作是不会修改seg$的ts#、file#、block#的,具体测试过程如下:

所以需要恢复的元数据块和数据字典基表以及内容为:

  • segment header(dataobj#、LHWM、HHWM、extent map、aux map以及extents个数)
  • tab$(dataobj#)
  • obj$(dataobj#)

下面提供segment header的信息对应的offset:
segment header dump:

下面是我认为比较重要的segment header每个offset对应的含义:

offset desc
36 total extents
40 total blocks
48 HWM所在的ext#
52 HWM所在的ext#的第几个block(从0开始)
56 HWM所在的ext#的ext blocks
60 HWM所在的dba地址
76 HWM下有多少个block
92 LHWM所在的ext#
96 LHWM所在的ext#的第几个block(从0开始)
100 LHWM所在的ext#的ext size
104 LHWM所在的dba地址
120 LHWM下有多少个block
124 Level 1 BMB for High HWM block
128 Level 1 BMB for Low HWM block
213 block size
220 L2 Array start offset
224 First Level 3 BMB
228 L2 Hint for inserts
236 Last Level 1 BMB
240 Last Level II BMB
244 Last Level III BMB
264 Map Header的extents
272 Map Header的obj#
276 Map Header的flag
280 ext#为0的block_id
284 ext#为0的extent blocks
288 ext#为1的block_id
292 ext#为1的extent blocks
……以此类推循环
2736 aux map信息,ext#为0的L1 dba
2740 aux map信息,ext#为0的data dba
2744 aux map信息,ext#为1的L1 dba
2748 aux map信息,ext#为1的data dba
……以此类推循环
5192 Second Level Bitmap block DBAs

确认了需要恢复的内容之后,还需要确认是否有对象占用了truncate释放的空间,依据是用从redo dump找到的truncate前的extent map和dba_extents对比。如果有对象占用需要先move对象到其他表空间。如何从redo dump找到extent map见后面段头块的extent map恢复。

无覆盖的TRUNCATE恢复

重要:假如确实失误truncate了表,需要马上停应用,最好将表空间设置为offline或者read only,避免数据被覆盖

通过之前对table full scan、segment header和前面truncate原理的分析,tfs不会读取L1,L2块,所以恢复的时候L1,L2块和具体存放数据的块都不用管,只需尝试通过修改段头块和基表信息来恢复truncate的数据,bbed修改段头块的具体offset含义见segment header章节。

1.修改段头块dataobj#(由于表可能不止一次被truncate,所以获取之前dataobj#最好的办法是通过logminer或者redo dump,这里我使用的redo dump)

从redo dump信息可以看到段头块0x0140076a的dataobj#从16840变成了16860,所以这里需要将段头块的dataobj#改回16840

仅仅修改段头块的dataobj#再次查询表会报ORA-08103: object no longer exists,原因是基表没有修改。查询的时候会通过表名去找到该表的dataobj#

2.恢复段头块HWM(HWM信息可以从redo dump中获取)

可以看到段头块0x0140076a的LHWM信息和HHWM是一样的,都是从
Highwater:: 0x01405b83 ext#: 27 blk#: 3 ext size: 128
变成了
Highwater:: 0x0140076b ext#: 0 blk#: 3 ext size: 8
所以这里恢复HWM信息只需要根据redo dump改回去即可

3.恢复段头块extent map,Auxillary Map以及extent个数(extent信息和aux map信息同样可以从redo dump中获取)

从redo dump可以发现truncate操作对于extent map和aux map是从最后一个extent开始逐一删除的,这里可以看到该表的extent总共有28个,ext#为27是该表最后一个extent,block_id为0x1405b80,该extent size为128个块,以此类推很容易可以通过简单的grep找出extent map;同理ext#为27的aux map的L1 dba为x01405b80,data dba为0x01405b82,以此类推很容易可以通过简单的grep找出aux map

这里可以写脚本根据segment header章节offset的含义来生成bbed命令,由于bbed命令较长省略一部分

最后恢复成功,这里由于L1,L2没有恢复,所以insert会有问题,但是可以通过CTAS重建表完全恢复

最后恢复成功。

此条目发表在Oracle, Oracle Recover分类目录,贴了, 标签。将固定链接加入收藏夹。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注