--(南宋)陆游《冬夜读书示子聿》
前 言
01
当面试官问你,Oracle数据库的表被执行了delete操作并已提交,如何找回数据呢?常规的修复方法包括闪回表、impdp单表导入、rman单表恢复,除了这些方法之外,还可以考虑使用BBED工具找回数据。今天我想跟大家先分享下如何运用bbed工具恢复delete误删除的数据。想深入学习bbed工具的朋友,一定要动手多做实验!
当Oracle数据库执行delete语句后,表中数据实际上并没有被删除,而是将行标记为已删除,表示数据块可以重用。因此,在数据块没有被覆盖之前,将数据块上的标志位的已删除标记去掉,就可以找回数据了。
往期文章推荐
02
1、DBA实验手册第1讲 运用bbed工具和SQL语句学习bootstrap$表存储信息
2、DBA实验手册第2讲 运用bbed工具恢复bootstrap$表数据
欢迎关注公众号!
数据块简介
03
在Oracle数据库中,块是存储数据的最小单位,一个块通常大小为8KB。块是Oracle进行I/O操作的基本单位,它用于存储表数据、索引、回滚段数据、临时数据等。数据库中的所有数据都存储在块中,包括表的行数据、索引键值和数据字典信息等。每个数据块的块头结构都是固定的。所有的数据块都包含相同的头结构----kcbh。行状态存储在每行的行标头中,该行标头占用每行的前几个字节。行标头由行标记、锁字节(ITL条目)和列计数组成。第一个Row标志是一个单字节,它保存一个显示行状态的位掩码。位掩码解码如下:
因此行标头(数据块dump文件中的“--H-FL--”对应上面表格的8列信息)=Head of row picec + First data picec + Last data picec = 32+8+4 = 44 转换为16进制 0x2c。当数据被删除后,行标头+(Deleted 16) =32+16+8+4=60 转换为16进制 0x3c。
SQL> select to_char(44,'xxxxxxxx') as hex1,
to_char(60,'xxxxxxxx') as hex2 from dual;
HEX1 HEX2
--------- ---------
2c 3c
因此,当数据未被覆盖时,修改行标头0x3c为0x2c即可恢复。
上面的内容你看明白了没有,如果没理解,可以再认真读一遍,然后再阅读下面的内容。
实验环境准备
04
数据库版本:Oracle 12.1.0.2.0 单机版
BBED版本:BBED 2.0.0.0.0
操作系统:Red Hat Enterprise Linux Server 6.5
若想获取实验环境相关的软件包、文档资料以及批量恢复脚本,可以查看文章的附件部分。
1、创建测试表空间和测试用户
SQL> select file_name from dba_data_FILES;
SQL> create tablespace mytbs datafile '/u01/app/oracle/oradata/PROD4/mytbs.dbf' size 50m;
SQL> create user myuser identified by oracle default tablespace mytbs;
SQL> grant resource,connect, select any dictionary to myuser;
SQL> alter user myuser quota unlimited on mytbs;
2、创建测试表与数据,这边只模拟2行记录,这样的话后面dump出来的日志会简短些。
SQL> create table mytable (id number,name varchar2(10));
SQL> insert into mytable values(100,'dba1');
SQL> insert into mytable values(200,'dba2');
3、刷新缓存,让脏数据落盘
SQL> alter system flush buffer_cache;
System altered.
第1次dump数据块
05
1、查看表数据分布情况
1 数据文件号(dba_objects)
SQL> select object_id from dba_objects
where object_name = 'MYTABLE' and owner = 'MYUSER';
OBJECT_ID
----------
93996
2、数据分布在2号文件的133数据块
SQL> select distinct dbms_rowid.rowid_relative_fno(rowid) rfno,
dbms_rowid.rowid_block_number(rowid) blocknum
from myuser.mytable order by 2;
RFNO BLOCKNUM
---------- ----------
2 133
3、表存储在2号文件的第1个区,从128号数据块开始,占用8个块,共65536字节(8k*8个)
SQL> select EXTENT_ID,FILE_ID,BLOCK_ID,BYTES,BLOCKS
from dba_extents where segment_name='MYTABLE';
EXTENT_ID FILE_ID BLOCK_ID BYTES BLOCKS
---------- ---------- ---------- ---------- ----------
0 2 128 65536 8
4、数据段基本信息
SQL> col segment_name for a15
SQL> col tablespace_name for a15
SQL> select segment_name,tablespace_name,header_file,header_block
from dba_segments where segment_name='MYTABLE';
SEGMENT_NAME TABLESPACE_NAME HEADER_FILE HEADER_BLOCK
--------------- --------------- ----------- ------------
MYTABLE MYTBS 2 130
2、数据块转储
转储MYTABLE表的133数据块,查看删除之前的内容。
1、转储命令
SQL> alter system dump datafile 2 block 133;
System altered.
SQL> select value from v$diag_info where name ='Default Trace File';
VALUE
--------------------------------------------------------------------------------
/u01/app/oracle/diag/rdbms/prod4/PROD4/trace/PROD4_ora_85254.trc
2、133数据块转储内容
没有执行alter system flush buffer_cache,即脏数据未落盘
Start dump data blocks tsn: 9 file#:2 minblk 133 maxblk 133
Block dump from cache:【dump出缓存cache的内容】
Dump of buffer cache at level 4 for pdb=0 tsn=9 rdba=8388741
BH (0x77ff6bd8) file#: 2 rdba: 0x00800085 (2/133) class: 1 ba: 0x77f48000
set: 5 pool: 3 bsz: 8192 bsi: 0 sflg: 0 pwc: 0,0
dbwrid: 0 obj: 93996 objn: 93996 tsn: [0/9] afn: 2 hint: f
hash: [0x7cbe8e28,0x7cbe8e28] lru: [0x77ff6e00,0x77ff6b80]
obj-flags: object_ckpt_list
ckptq: [0x77ff6a98,0x77ff6d18] fileq: [0x77ff6aa8,0x77ff6d28]
objq: [0x77ff6e28,0x77ff6ba8] objaq: [0x77ff6e38,0x77ff6bb8]
st: XCURRENT md: NULL fpin: 'ktspbwh2: ktspfmdb' fscn: 0x0.3d4778 tch: 4
flags: buffer_dirty
LRBA: [0x2.1f1b.0] LSCN: [0x0.3d4778] HSCN: [0x0.3d477c] HSUB: [1]
Block dump from disk:【dump出磁盘的内容】
Encrypted block content will not be dumped. Dumping header only.
buffer tsn: 9 rdba: 0x00000085 (0/133)
scn: 0x0.0 seq: 0x01 flg: 0x05 tail: 0x00000001
frmt: 0x02 chkval: 0xa785 type: 0x00=unknown
Hex dump of initial block header 1 = INITIAL
Dump of memory from 0x00007FA34753E200 to 0x00007FA34753E214
7FA34753E200 0000A200 00000085 00000000 05010000 [................]
7FA34753E210 0000A785 [....]
End dump data blocks tsn: 9 file#: 2 minblk 133 maxblk 133
3、133数据块转储内容
执行alter system flush buffer_cache,即脏数据已落盘
Start dump data blocks tsn: 9 file#:2 minblk 133 maxblk 133
Block dump from cache:【dump出缓存cache的内容】
Dump of buffer cache at level 4 for pdb=0 tsn=9 rdba=8388741
BH (0x77ff6bd8) file#: 2 rdba: 0x00800085 (2/133) class: 1 ba: 0x77f48000
set: 5 pool: 3 bsz: 8192 bsi: 0 sflg: 0 pwc: 0,0
dbwrid: 0 obj: 93996 objn: 93996 tsn: [0/9] afn: 2 hint: f
hash: [0x7cbe8e28,0x7cbe8e28] lru: [0x753dcc80,0x743da840]
lru-flags: on_auxiliary_list
ckptq: [NULL] fileq: [NULL]
objq: [NULL] objaq: [NULL]
st: FREE md: NULL fpin: 'ktspbwh2: ktspfmdb' fscn: 0x0.3d4778 tch: 0 lfb: 33
flags:
Block dump from disk:【dump出磁盘的内容】
buffer tsn: 9 rdba: 0x00800085 (2/133)
scn: 0x0.3d477c seq: 0x01 flg: 0x06 tail: 0x477c0601
frmt: 0x02 chkval: 0xbd31 type: 0x06=trans data
Hex dump of block: st=0, typ_found=1
Dump of memory from 0x00007FD4F7ECA200 to 0x00007FD4F7ECC200
7FD4F7ECA200 0000A206 00800085 003D477C 06010000 [........|G=.....]
7FD4F7ECA210 0000BD31 00000001 00016F2C 003D4778 [1.......,o..xG=.]
7FD4F7ECA220 00000000 00320002 00800080 00030007 [......2.........]
7FD4F7ECA230 000006C4 0100092A 00040123 00002002 [....*...#.... ..]
7FD4F7ECA240 003D477C 00000000 00000000 00000000 [|G=.............]
7FD4F7ECA250 00000000 00000000 00000000 00000000 [................]
7FD4F7ECA260 00000000 00020100 0016FFFF 1F6C1F82 [..............l.]
7FD4F7ECA270 00001F6C 1F8D0002 00001F82 00000000 [l...............]
7FD4F7ECA280 00000000 00000000 00000000 00000000 [................]
Repeat 501 times
7FD4F7ECC1E0 00000000 012C0000 03C20202 61626404 [......,......dba]
7FD4F7ECC1F0 02012C32 0402C202 31616264 477C0601 [2,......dba1..|G]
Block header dump: 0x00800085
Object id on Block? Y
seg/obj: 0x16f2c csc: 0x00.3d4778 itc: 2 flg: E typ: 1 - DATA
brn: 0 bdba: 0x800080 ver: 0x01 opc: 0
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0007.003.000006c4 0x0100092a.0123.04 --U- 2 fsc 0x0000.003d477c
0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
bdba: 0x00800085
data_block_dump,data header at 0x7fd4f7eca264
===============
tsiz: 0x1f98
hsiz: 0x16
pbl: 0x7fd4f7eca264
76543210
flag=--------
ntab=1
nrow=2 【表示有2行数据】
frre=-1
fsbo=0x16
fseo=0x1f82
avsp=0x1f6c
tosp=0x1f6c
0xe:pti[0] nrow=2 offs=0
0x12:pri[0] offs=0x1f8d
0x14:pri[1] offs=0x1f82
block_row_dump: 【每行记录信息】
tab 0, row 0, @0x1f8d 【第1行记录】
tl: 11 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c2 02
col 1: [ 4] 64 62 61 31
tab 0, row 1, @0x1f82 【第2行记录】
tl: 11 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c2 03
col 1: [ 4] 64 62 61 32
end_of_block_dump
End dump data blocks tsn: 9 file#: 2 minblk 133 maxblk 133
3、通过bbed查看数据文件结构体信息
BBED> set dba 2,133 【定位到2,133数据块】
DBA 0x00800085 (8388741 2,133)
BBED> p
kcbh.type_kcbh
--------------
ub1 type_kcbh @0 0x06
BBED> map
File: /u01/app/oracle/oradata/PROD4/mytbs.dbf (2)
Block: 133 Dba:0x00800085
------------------------------------------------------------
KTB Data Block (Table/Cluster)
struct kcbh, 20 bytes @0 【块头】
struct ktbbh, 72 bytes @20 【事务层头结构】
struct kdbh, 14 bytes @100 【事务型数据(data类型)的数据层头结构】
struct kdbt[1], 4 bytes @114 【数据层表索引】
sb2 kdbr[2] @118 【数据层行索引】
ub1 freespace[8044] @122 【空闲空间】
ub1 rowdata[22] @8166 【数据存放内容】
ub4 tailchk @8188 【尾块】
BBED> p kdbr 【行索引,行索引也是一个数组,定义了该块中包含的所有行数据的位置。行索引的插槽由2个字节组成】
sb2 kdbr[0] @118 8077
sb2 kdbr[1] @120 8066
BBED> p *kdbr[0] 【查看行索引】
rowdata[11]
-----------
ub1 rowdata[11] @8177 0x2c 【0x2c表示标志位是--H-FL--(32+8+4 = 44转换成16进制为2c)】
BBED> x rnc *kdbr[0]
rowdata[11] @8177
-----------
flag@8177: 0x2c (KDRHFL, KDRHFF, KDRHFH)
lock@8178: 0x01
cols@8179: 2
col 0[2] @8180: 100 【第1条记录 第1列数据】
col 1[4] @8183: dba1 【第1条记录 第2列数据】
BBED> p *kdbr[1]
rowdata[0]
----------
ub1 rowdata[0] @8166 0x2c 【0x2c表示标志位是--H-FL--(32+8+4 = 44转换成16进制为2c)】
BBED> x rnc *kdbr[1]
rowdata[0] @8166
----------
flag@8166: 0x2c (KDRHFL, KDRHFF, KDRHFH)
lock@8167: 0x01
cols@8168: 2
col 0[2] @8169: 200 【第2条记录 第1列数据】
col 1[4] @8172: dba2 【第2条记录 第2列数据】
模拟数据丢失
06
删除测试表记录,模拟delete误删除操作
SQL> delete from myuser.mytable;
2 rows deleted.
SQL> commit;
Commit complete.
SQL> alter system flush buffer_cache;
System altered.
第2次dump数据块
07
1、再次转储数据块,查看数据删除后的信息
1、转储数据块操作
SQL> alter system dump datafile 2 block 133;
System altered.
SQL> select value from v$diag_info
where name ='Default Trace File';
VALUE
--------------------------------------------------------------------------------
/u01/app/oracle/diag/rdbms/prod4/PROD4/trace/PROD4_ora_120104.trc
2、具体的trace日志内容
*** 2024-06-06 23:40:52.588
Start dump data blocks tsn: 9 file#:2 minblk 133 maxblk 133
Block dump from cache:
Dump of buffer cache at level 4 for pdb=0 tsn=9 rdba=8388741
BH (0x7a7f6f98) file#: 2 rdba: 0x00800085 (2/133) class: 1 ba: 0x7a74e000
set: 5 pool: 3 bsz: 8192 bsi: 0 sflg: 0 pwc: 0,0
dbwrid: 0 obj: 93996 objn: 93996 tsn: [0/9] afn: 2 hint: f
hash: [0x7cbe8e28,0x7cbe8e28] lru: [0x74be1b40,0x783d6600]
lru-flags: moved_to_tail on_auxiliary_list
ckptq: [NULL] fileq: [NULL]
objq: [NULL] objaq: [NULL]
st: FREE md: NULL fpin: 'kdswh01: kdstgr' fscn: 0x0.0 tch: 0 lfb: 33
flags:
Block dump from disk:
buffer tsn: 9 rdba: 0x00800085 (2/133)
scn: 0x0.3dc57b seq: 0x01 flg: 0x06 tail: 0xc57b0601
frmt: 0x02 chkval: 0x706c type: 0x06=trans data
Hex dump of block: st=0, typ_found=1
Dump of memory from 0x00007F717BCC2200 to 0x00007F717BCC4200
7F717BCC2200 0000A206 00800085 003DC57B 06010000 [........{.=.....]
7F717BCC2210 0000706C 00000001 00016F2C 003DC57A [lp......,o..z.=.]
7F717BCC2220 00000000 00320002 00800080 00030007 [......2.........]
7F717BCC2230 000006C4 0100092A 00040123 00008000 [....*...#.......]
7F717BCC2240 003D477C 001A0009 0000081B 010011E8 [|G=.............]
7F717BCC2250 002C013A 00122002 003DC57B 00000000 [:.,.. ..{.=.....]
7F717BCC2260 00000000 00020100 0016FFFF 1F6C1F82 [..............l.]
7F717BCC2270 00001F82 1F8D0002 00001F82 00000000 [................]
7F717BCC2280 00000000 00000000 00000000 00000000 [................]
Repeat 501 times
7F717BCC41E0 00000000 023C0000 03C20202 61626404 [...... set offset 8177 【从上述日志文件可知,第1行的偏移位置为8177】
OFFSET 8177
BBED> d v
File: u01/app/oracle/oradata/PROD4/mytbs.dbf (2)
Block: 133 Offsets: 8177 to 8191 Dba:0x00800085
-------------------------------------------------------
3c020202 c2020464 62613101 067bc5 l m x 2c offset 8177
File: u01/app/oracle/oradata/PROD4/mytbs.dbf (2)
Block: 133 Offsets: 8177 to 8191 Dba:0x00800085
------------------------------------------------------------------------
2c020202 c2020464 62613101 067bc5
BBED> set offset 8166 【从上述日志文件可知,第2行的偏移位置为8166】
OFFSET 8166
BBED> d /v
File: /u01/app/oracle/oradata/PROD4/mytbs.dbf (2)
Block: 133 Offsets: 8166 to 8191 Dba:0x00800085
-------------------------------------------------------
3c020202 c2030464 6261322c 020202c2 l m /x 2c offset 8166
File: /u01/app/oracle/oradata/PROD4/mytbs.dbf (2)
Block: 133 Offsets: 8166 to 8191 Dba:0x00800085
------------------------------------------------------------------------
2c020202 c2030464 6261322c 020202c2 02046462 61310106 7bc5
BBED> sum apply;
Check value for File 2, Block 133:
current = 0x607c, required = 0x607c
2、验证数据已恢复出来
SQL> alter system flush buffer_cache;
System altered.
SQL> select * from myuser.mytable;
ID NAME
---------- ----------
100 dba1
200 dba2
实验总结
10
当Oracle数据库执行delete语句后,表中数据实际上并没有被删除,而是将行标记为已删除,表示数据块可以重用。因此,在数据块没有被覆盖之前,将数据块上的标志位的已删除标记去掉,就可以找回数据了。通过对比数据块删除数据前、后两次转储信息,可以知道块中行存在数据时标志位是fb: --H-FL--,行数据被删除后标志位变成fb: --HDFL--,换行成16进制,标志位改成0x2c表示存在数据。通过上述实验,我们在数据未被覆盖时,修改行标头0x3c为0x2c,然后把数据恢复出来。bbed恢复过程总结如下: