如果说hellodba发明的truncate的手段恢复为移花接木,那么以下的手段可以称之为偷天换日,在此感谢伟翔同学的信息提供以及建议。
对于Truncate恢复,总结以下步骤:
1.遍历所有数据文件的数据块,寻找offset 1是23的(16进制),23代表段头块,同时还要和相应的Data Object Id相同的,这个需要检索offset 272的位置。(直接sqlplus就可以实现了)
2.找到了段头块,我们就可以通过offset 5192寻找到我们的L2块。
3.找到了L2块,我们就可以通过offset 116找到所有的L1块。找到了L1块,我们就等于找到了数据块。
4.至此,我们就可以开始反向的构造段头块。
5.修改段头块、L2块和第一个L1块的Data Object Id.,同时在修改数据字典。
6.修改段头块的高水位信息。当然这里的高水位块的辨别,一定会是在最后一个Extents上,你可以设置到最后一个Extnets的最后一个块,这个信息的准备性其实无所谓,全表扫描的时候它一定会扫描这个块下面所有的块。
7.修改段头上的Extents信息。
而在这些之前对segment header 结构的熟悉是必须的。
测试truncate恢复过程如下:
首先恢复段头,L1,L2等object_data_id为原object_data_id
BBED> set block 130 BLOCK# 130 BBED> modify /x ca offset 272 Warning: contents of previous BIFILE will be lost. Proceed? (Y/N) y File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 272 to 291 Dba:0x00000000 ------------------------------------------------------------------------ ca270100 00000010 80004001 08000000 00000000 BBED> set block 129 BLOCK# 129 BBED> modify /x ca offset 104 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 129 Offsets: 104 to 123 Dba:0x00000000 ------------------------------------------------------------------------ ca270100 01000000 00000000 80004001 05000100 BBED> set block 128 BLOCK# 128 BBED> modify /x ca offset 192 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 128 Offsets: 192 to 211 Dba:0x00000000 ------------------------------------------------------------------------ ca270100 e6151400 00000000 80004001 08000000 BBED> sum apply Check value for File 0, Block 128: current = 0x35a9, required = 0x35a9
还需要修改数据字典,否则会报错。
SQL> select * from a1.a; select * from a1.a * ERROR at line 1: ORA-08103: object no longer exists SQL> update OBJ$ set DATAOBJ#= 75722 where OBJ#= 75722; 1 row updated. SQL> commit; Commit complete. SQL> update TAB$ set DATAOBJ#=75722 where OBJ#=75722; 1 row updated. SQL> commit; Commit complete. SQL> update SEG$ set HWMINCR=75722 where FILE#=5 and BLOCK#=130; -- 这里的130为段头 1 row updated. SQL> commit; Commit complete. SQL> shutdown immediate; Database closed. Database dismounted. ORACLE instance shut down. SQL> startup ORACLE instance started. Total System Global Area 839282688 bytes Fixed Size 2233000 bytes Variable Size 511708504 bytes Database Buffers 322961408 bytes Redo Buffers 2379776 bytes Database mounted. Database opened. SQL> select * from a1.a; no rows selected
2.段头高水位信息恢复
修改了这些东西后,我们会发现数据还是没有。我们还需要修改一些信息。一个很重要的信息就是段头上的高水位信息。在Truncate之前,段头上会记载。前面2节内容在解析L1块的时候说明过:
可以看到223后面直接就是225,直接此处跳空,这是因为我们的224是L1位图块,后面紧跟着我们刚刚说的225,226,227,228,229,230,231,232,233。但是问题是,这里看不到后面的234到239?这是因为234到239还是空闲没有格式化过的块,但是它已经被L1锁定了。
所以我们现在的高水位的块是234,一般做全表扫描的查询就会查高水位以下(234)的块。我们来看下我们现在的高水位。
BBED> dump /v offset 48 count 16 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 48 to 63 Dba:0x00000000 ------------------------------------------------------- 00000000 03000000 08000000 83004001 l ..............@. BBED> dump /v offset 92 count 16 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 92 to 107 Dba:0x00000000 ------------------------------------------------------- 00000000 03000000 08000000 83004001 l ..............@. SQL> select dbms_utility.data_block_address_file(to_number('01400083','xxxxxxxx')) as fileno,dbms_utility.data_block_address_block(to_number('01400083','xxxxxxxx')) as blockno from dual; FILENO BLOCKNO ---------- ---------- 5 131
注意看这里的高水位是83004001,转换成文件号和块号,刚好是文件5块131。而前面的00000000,03000000,则代表着是扩展0,block 3,代表着高水位的位置。刚好是第一个extent的第三个块。128是L1,129是L2,130是段头块。而131则是第一个可以使用的数据块。所以这里记录了extent 0,block为3则代表了文件5的131号块。而08000000则代表了extent的大小,我们每个extents是由8个块组成的。
那在truncate之前,我们的高水位的块是文件5块234,我们从块128开始,每8个块是一个extent,234是第14个extent的第三个块。后面的6个块是没有插入数据的空块。这个在前面我dump 最后一个L1块得知。回顾一下,这里下面的块状态显示11111111 11000000。
BBED> dump /v offset 396 count 50 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 224 Offsets: 396 to 445 Dba:0x00000000 ------------------------------------------------------- 11111111 11000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 0000 l ..
所以我们这个地方要把高水位从00000000 03000000 08000000 83004001修改成0d000000 02000000 08000000 ea004001。0d000000代表13,表明是第十四个扩展,02000000代表02,表明是第三个块开始,而08000000还是一样代表着这个扩展是8个块的大小,而ea004001则代表着文件5块234。
BBED> modify /x 0d offset 48 Warning: contents of previous BIFILE will be lost. Proceed? (Y/N) y File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 48 to 63 Dba:0x00000000 ------------------------------------------------------------------------ 0d000000 03000000 08000000 83004001 BBED> modify /x 02 offset 52 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 52 to 67 Dba:0x00000000 ------------------------------------------------------------------------ 02000000 08000000 83004001 00000000 BBED> modify /x ea offset 60 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 60 to 75 Dba:0x00000000 ------------------------------------------------------------------------ ea004001 00000000 00000000 00000000 BBED> dump /v offset 48 count 16 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 48 to 63 Dba:0x00000000 ------------------------------------------------------- 0d000000 02000000 08000000 ea004001 l ............ BBED> modify /x 0d offset 92 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 92 to 107 Dba:0x00000000 ------------------------------------------------------------------------ 0d000000 03000000 08000000 83004001 BBED> modify /x 02 offset 96 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 96 to 111 Dba:0x00000000 ------------------------------------------------------------------------ 02000000 08000000 83004001 00000000 BBED> modify /x ea offset 104 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 104 to 119 Dba:0x00000000 ------------------------------------------------------------------------ ea004001 00000000 00000000 00000000 BBED> dump /v offset 92 count 16 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 92 to 107 Dba:0x00000000 ------------------------------------------------------- 0d000000 02000000 08000000 ea004001 l ............ BBED> sum apply Check value for File 0, Block 130: current = 0x8c54, required = 0x8c54
修改完成之后,刷新buffer cache,然后重新查询。
SQL> alter system flush buffer_cache; System altered. SQL> select count(1) from a1.a; COUNT(1) ---------- 142
3.Extents信息恢复
可以看到数据量不对,这是因为我们Truncate之后,在段头上只剩下了一个Extent的信息。而我们的Extents是有14个的,这需要我们在修改如下几个地方。Offset 264代表着我们Extents的数量,,这里修改成0e代表了14个extents。
BBED> modify /x 0e offset 264 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 264 to 363 Dba:0x00000000 ------------------------------------------------------------------------ 0e000000 00000000 ca270100 00000010 80004001 08000000 88004001 08000000 90004001 08000000 98004001 08000000 a0004001 08000000 a8004001 08000000 b0004001 08000000 b8004001 08000000 c0004001 08000000 c8004001 08000000 d0004001
修改完Extents的数量之后,还需要添加对应的Extents Map的信息。因为我们的Extents Map信息也被删除了。从我们的offset 280开始。
BBED> dump /v offset 280 count 100 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 280 to 379 Dba:0x00000000 ------------------------------------------------------- 80004001 08000000 00000000 00000000 l ..@............. 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 l ....
80004001 08000000这个Extents是我们第一个Extents。代表了文件5的块128。我们可以依次类推下列的信息出来。而08000000则代表有8个数据块构成一个Extents。
modify /x 8800 offset 288 modify /x 4001 offset 290 modify /x 08 offset 292 modify /x 9000 offset 296 modify /x 4001 offset 298 modify /x 08 offset 300 modify /x 9800 offset 304 modify /x 4001 offset 306 modify /x 08 offset 308 modify /x a000 offset 312 modify /x 4001 offset 314 modify /x 08 offset 316 modify /x a800 offset 320 modify /x 4001 offset 322 modify /x 08 offset 324 modify /x b000 offset 328 modify /x 4001 offset 330 modify /x 08 offset 332 modify /x b800 offset 336 modify /x 4001 offset 338 modify /x 08 offset 340 modify /x c000 offset 344 modify /x 4001 offset 346 modify /x 08 offset 348 modify /x c800 offset 352 modify /x 4001 offset 354 modify /x 08 offset 356 modify /x d000 offset 360 modify /x 4001 offset 362 modify /x 08 offset 364 modify /x d800 offset 368 modify /x 4001 offset 370 modify /x 08 offset 372 modify /x e000 offset 376 modify /x 4001 offset 378 modify /x 08 offset 380 modify /x e800 offset 384 modify /x 4001 offset 386 modify /x 08 offset 388 BBED> dump /v offset 280 count 128 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 280 to 407 Dba:0x00000000 ------------------------------------------------------- 80004001 08000000 88004001 08000000 l ..@.......@..... 90004001 08000000 98004001 08000000 l ..@.......@..... a0004001 08000000 a8004001 08000000 l ........ b0004001 08000000 b8004001 08000000 l ........ c0004001 08000000 c8004001 08000000 l ........ d0004001 08000000 d8004001 08000000 l ........ e0004001 08000000 e8004001 08000000 l ........ 00000000 00000000 00000000 00000000 l ................ <16 bytes per line>
当然这些Extents Map修改完成之后,我们还需要在添加Auxillary Map。从Offset 2736开始。
BBED> dump /v offset 2736 count 200 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 2736 to 2935 Dba:0x00000000 ------------------------------------------------------- 80004001 83004001 00000000 00000000 l ..@...@......... 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ................ 00000000 00000000 00000000 00000000 l ...............
这里的80004001 83004001,代表这Extents中,L1块的地址和Data Block的地址,可以看到在Extents 1上面,它的L1块是128,而数据块是从131开始的。因为我们的129是L2,130是段头。所以下面我们构造其他的数据的时候,我们也要遵循这个规律。我们的Extents 2,它的L1还是128块,但是它的数据块却是从136开始的。而Extents 3,它的L1就是第二个L1块,也就是144,而它的数据块的开始则是从145开始的。依次类推下去。结果如下:
modify /x 8000 offset 2744 modify /x 4001 offset 2746 modify /x 8800 offset 2748 modify /x 4001 offset 2750 modify /x 9000 offset 2752 modify /x 9000 offset 2754 modify /x 9100 offset 2756 modify /x 4001 offset 2758 modify /x 9000 offset 2760 modify /x 4001 offset 2762 modify /x 9800 offset 2764 modify /x 4001 offset 2766 modify /x a000 offset 2768 modify /x 4001 offset 2770 modify /x a100 offset 2772 modify /x 4001 offset 2774 modify /x a000 offset 2776 modify /x 4001 offset 2778 modify /x a800 offset 2780 modify /x 4001 offset 2782 modify /x b000 offset 2784 modify /x 4001 offset 2786 modify /x b100 offset 2788 modify /x 4001 offset 2790 modify /x b000 offset 2792 modify /x 4001 offset 2794 modify /x b800 offset 2796 modify /x 4001 offset 2798 modify /x c000 offset 2800 modify /x 4001 offset 2802 modify /x c100 offset 2804 modify /x 4001 offset 2806 modify /x c000 offset 2808 modify /x 4001 offset 2810 modify /x c800 offset 2812 modify /x 4001 offset 2814 modify /x d000 offset 2816 modify /x 4001 offset 2818 modify /x d100 offset 2820 modify /x 4001 offset 2822 modify /x d000 offset 2824 modify /x 4001 offset 2826 modify /x d800 offset 2828 modify /x 4001 offset 2830 modify /x e000 offset 2832 modify /x 4001 offset 2834 modify /x e100 offset 2836 modify /x 4001 offset 2838 modify /x e000 offset 2840 modify /x 4001 offset 2842 modify /x e800 offset 2844 modify /x 4001 offset 2846 BBED> dump /v offset 2736 count 128 File: /oracle/app/oracle/oradata/ora11/a1.dbf (0) Block: 130 Offsets: 2736 to 2863 Dba:0x00000000 ------------------------------------------------------- 80004001 83004001 80004001 88004001 l ..@...@...@...@. 90009000 91004001 90004001 98004001 l ......@...@...@. a0004001 a1004001 a0004001 a8004001 l b0004001 b1004001 b0004001 b8004001 l c0004001 c1004001 c0004001 c8004001 l d0004001 d1004001 d0004001 d8004001 l e0004001 e1004001 e0004001 e8004001 l 00000000 00000000 00000000 00000000 l ................
修改完成这些后,我们就能够查到我们全部的数据了。
SQL> alter system flush buffer_cache; System altered. SQL> select count(1) from a1.a; COUNT(1) ---------- 2775
此时切勿执行一些其他的操作,应该尽快的使用CTAS的方式将这个表进行备份或者是导出。因为段头块L2和L1的信息还一些是没有修改的。
SQL> create table a2.a as select * from a1.a; Table created. SQL> drop table a1.a; Table dropped
至此,Truncate恢复完成。仔细的研究才会发现,其实最重要的是摸清楚整个段的构造情况,只要你对整个段的构造情况,了若指掌,基本上恢复是很简单的。