概念描述
# 能使用dd从asmcmd中抽取数据文件,在前2个AU(AU0和AU1)被完全破坏的情况下,需要磁盘组asm的元数据FDIR完整
# 同样的功能,AMDU也能实现
# 使用dd,可以模拟asmcmd的cp,解析amdu的抽取过程,更加直观的了解数据文件在磁盘组中的底层组织结构
测试验证
# 本次模拟测试步骤:
# 1 使用3个磁盘组成的Normal磁盘组,再添加一块磁盘(raw4)之后,进行表空间创建,插入数据,
# 2 offline掉新添加的那个磁盘4
# 3 进行更新数据,提交到磁盘
# 4 进行关库,使用dd抽取文件,
# 5 使用抽取出来的数据文件进行使用验证
开始之前准备
# 在磁盘组上创建表空间TS1,新建一个测试表cog.t1
SYS@DB(db): 1> @tbsfile ts1
FILE_ID FILE_NAME
------- ------------------------------------
5 +FRA/db/datafile/ts1.256.1123912897
SYS@DB(db): 1> select count(1) from cog.t1;
COUNT(1)
--------
25000
# offline表空间
# 查到的ext#map,由于3号磁盘被offline,所有AU号在Fdir中被修改为了4294967294,需要读其它镜像
alter tablespace ts1 offline;
# 更新cog.t1表数据
insert ...
update ...
commit;
# 关闭数据库
shutdown immediate
实验过程
1 遍历磁盘头,找出FDIR所在的AU
# 1 遍历磁盘头,找出FDIR所在的AU
# 元数据NORMAL默认是3副本
# 读前100个AU的0号块
# cat dsk.list
/dev/raw/raw1
/dev/raw/raw2
/dev/raw/raw3
/dev/raw/raw4
aus=1M
autypefile=dsk_autype.list; > dsk_autype.list
while read line
do
for (( i=0; i<=100; i++ )) ; do
au_type=`kfed read $line aus=${aus} aun=${i} blkn=0 | grep "^kfbh.type" | awk '{print $5}'`
echo "disk: ${line} aun: ${i} type: "${au_type} >> ${autypefile}
done
done < dsk.list
2 过滤出FDIR
#
cat ${autypefile} | egrep "KFBTYP_LISTHEAD|KFBTYP_FILEDIR"
# disk: /dev/raw/raw1 aun: 29 type: KFBTYP_LISTHEAD
# disk: /dev/raw/raw1 aun: 42 type: KFBTYP_FILEDIR
# disk: /dev/raw/raw2 aun: 2 type: KFBTYP_LISTHEAD
# disk: /dev/raw/raw2 aun: 22 type: KFBTYP_FILEDIR
# disk: /dev/raw/raw3 aun: 2 type: KFBTYP_LISTHEAD
# disk: /dev/raw/raw3 aun: 35 type: KFBTYP_FILEDIR
# KFBTYP_LISTHEAD 元数据全部是这个AU中
# KFBTYP_FILEDIR 用户数据从这个AU开始
3 读FDIR,找出数据文件的AU分布
# 用kfed读(抽取)出来 调用下面的函数(0号dsk的 29和42号AU)
> file1.list
kfed_read_fdir /dev/raw/raw1 29 1 file1.list # 先抽1号ext#,即1号文件 1号ext#全部是
### file: 1 flg: 1 ftype: 15 dxrs: 3 ixrs: 3
#File# PhyExt# VirtExt# copy# dsk# AU#
1 0 0 0 2 2
1 1 0 1 1 2
1 2 0 2 0 29 --> 1号ext# (副本号2)
1 3 1 0 0 42 --> 2号ext# (副本号0)
1 4 1 1 1 22
1 5 1 2 2 35
# 用户文件,读取file1的第2个AU就可以了
aus=1M
fdir_file=fdir.list;>${fdir_file}
for (( i=0; i<=255; i++ )) ; do
kfed_read_fdir /dev/raw/raw1 42 $i ${fdir_file}
done
# 下面是要调用的shell函数
# 需要找的是文件号和ext号,还有冗余(用于计算是否是副本:2副本还是3副本)
kfed_read_fdir(){
# kfed read /dev/raw/raw1 aus=1M aun=29 blkn=1 | awk '/^kfbh.block.blk/{fno=$2}
# 传参 kfed_read_fdir /dev/raw/raw1 29 1
kfed read $1 aus=${aus} aun=$2 blkn=$3 | awk '/^kfbh.block.blk/{fno=$2}
/^kfffdb.lobytes/{bs=$2}
/^kfffdb.flags/{flg=$2}
/^kfffdb.fileType/{ftype=$2}
/^kfffdb.dXrs/{dxrs=substr($6,8,1)}
/^kfffdb.iXrs/{iXrs=substr($6,8,1);
if(bs>0 && ftype>0){print "### file: "fno" flg: "flg" ftype: "ftype" dxrs: "dxrs" ixrs: "iXrs"\n#File# PhyExt# VirtExt# copy# dsk# AU#"} }
/kfffde.*.xptr.au/{au=$2}
/kfffde\[.*\].xptr.disk/{match($1,/[0-9]+/);ext=substr($1,RSTART,RLENGTH);
dsk=$2; if(dxrs>0){vext=ext/dxrs;copy=ext%dxrs}fi;
if( ftype>0 && au<4294967295 && au>0 ){printf("%5d %8d %8d %5d %5d %5d\n",fno,ext,vext,copy,dsk,au) }}
' >> ${4}
}
cat ${fdir_file}
### file: 256 flg: 17 ftype: 2 dxrs: 2 ixrs: 3
#File# PhyExt# VirtExt# copy# dsk# AU#
256 0 0 0 0 20
256 1 0 1 1 25
256 2 1 0 3 39
...
256 56 28 0 0 60
256 57 28 1 2 59
256 58 29 0 3 4294967294 --> offline的磁盘,需要读副本
256 59 29 1 1 61
256 60 30 0 1 62
256 61 30 1 2 60
256 62 31 0 0 61
### file: 257 flg: 17 ftype: 2 dxrs: 2 ixrs: 3 --> 257号文件也读出来了
#File# PhyExt# VirtExt# copy# dsk# AU#
257 0 0 0 1 15
257 1 0 1 3 40
257 2 1 0 2 21
257 3 1 1 3 41
257 4 2 0 3 42
257 5 2 1 1 16
257 6 3 0 0 50
257 7 3 1 3 43
4 继续处理间接AU分布
# 60号以上的au处理-继续roll KFBTYP_INDIRECT
# 物理extent计算公式:e_ind=ext+e_ind+ofblk*480;
# 1M的 480*256=122880个AU=120G,所以只读1个就可以了
# 物理extent号必须连续,跳号则后续不处理
# 虚拟extent号的AU不能是4294967294,否则读其它副本
d0=/dev/raw/raw1
d1=/dev/raw/raw2
d2=/dev/raw/raw3
d3=/dev/raw/raw3
aus=1M
fdir2=fdir2.list;>${fdir2}
while read line
do
v_rem=`echo $line | awk '{if(index($1,"#")>0){print 1}else{print 0}}'`
if [ "${v_rem}" = 1 ] ; then
v_rem1=`echo $line | awk '{if(index($1,"###")>0){print 1}else{print 0}}'`
if [ "${v_rem1}" = 1 ] ; then
dxrs=`echo "$line" | awk '{print $9}'`
fi
echo "$line";per_ext=-1;per_ext1=0;
else
per_ext1=`expr ${per_ext} + 1`;read v_file v_ext v_vext v_cop v_dsk v_au <<< $line
# 为防止前60个AU物理ext#跳号,跳号则终止
if [ "${per_ext1}" = "${v_ext}" ] ; then per_ext=${per_ext1};
# 如果遇到offline的磁盘,输出副本所在的AU 前60个ext#
if [ ${v_cop} -eq 0 ] && [ ${v_au} -lt 4294967294 ] ; then copy_ture=0;
elif [ ${v_cop} -eq 0 ] && [ ${v_au} -eq 4294967294 ] ; then copy_ture=1;
elif [ ${v_cop} -eq 1 ] && [ ${v_au} -eq 4294967294 ] ; then copy_ture=2; fi
if [ ${v_ext} -eq 61 ] ; then v_cop=1; elif [ ${v_ext} -eq 62 ]; then v_cop=2; fi
if [ ${v_ext} -le 59 ] && [ ${v_cop} -eq ${copy_ture} ] ; then
echo "$line" >> ${fdir2}; copy_ture=0;
elif [ ${v_ext} -ge 60 ] && [ ${v_cop} -eq ${copy_ture} ] ; then
eval v_d=\${d${v_dsk}}; copy_ture=0
#echo "##exact indirect FDIR";bk=0
for (( i=0; i<=255; i++ )) ; do
kfed read ${v_d} aus=${aus} aun=${v_au} blkn=$i | awk -v file="${v_file}" -v ext="${v_ext}" -v dxrs="${dxrs}" '\
/^kfbh.block.blk/{ofblk=substr($5,4)}
/kffixe\[.*\].xptr.au/{match($1,/[0-9]+/);e_ind=substr($1,RSTART,RLENGTH);e_ind=ext+e_ind+ofblk*480;au=$2}
/kffixe\[.*\].xptr.disk/{dsk=$2;if(dxrs>0){vext=e_ind/dxrs;copy=e_ind%dxrs}fi;
if( au<4294967295 && au>0 ){printf("%d %8d %8d %5d %5d %5d\n",file,e_ind,vext,copy,dsk,au) }
}' >> ${fdir2}
done
# else echo "$line -- error \${v_ext}:${v_ext}-gt59 \${v_cop}=${v_cop} \${copy_ture}=${copy_ture}"
fi
else
continue;
fi
fi
done < ${fdir_file}
5 过滤掉1次offline磁盘
# 每个虚拟extent,只保留1条有效记录
# 如果遇到offline的磁盘,输出副本所在的AU 前60个ext#
fdir3=fdir3.list;>${fdir3}
while read line
do
read v_file v_ext v_vext v_cop v_dsk v_au <<< $line
if [ ${v_ext} -le 60 ]; then
echo "$line" >> ${fdir3};
else
if [ ${v_cop} -eq 0 ] && [ ${v_au} -lt 4294967294 ] ; then copy_ture=0;
elif [ ${v_cop} -eq 0 ] && [ ${v_au} -eq 4294967294 ] ; then copy_ture=1;
elif [ ${v_cop} -eq 1 ] && [ ${v_au} -eq 4294967294 ] ; then copy_ture=2; fi;
if [ "${v_cop}" = "${copy_ture}" ] ; then
echo "$line" >>${fdir3}; copy_ture=0;
fi
fi
done < fdir2.list
cat ${fdir3}
# 最终91个extent
[grid@db2 ~]$cat fdir3.list
256 0 0 0 0 20
256 2 1 0 3 39
256 4 2 0 1 12
256 6 3 0 2 25
256 8 4 0 0 51
256 11 5 1 0 52
256 12 6 0 1 53
256 14 7 0 2 53
256 16 8 0 0 54
256 19 9 1 1 54
256 20 10 0 1 55
256 22 11 0 2 54
256 24 12 0 0 96
256 27 13 1 0 97
256 28 14 0 1 97
256 30 15 0 2 96
256 32 16 0 0 98
256 35 17 1 1 99
256 36 18 0 1 56
256 38 19 0 2 98
256 40 20 0 0 56
256 43 21 1 2 99
256 44 22 0 1 57
256 46 23 0 2 56
256 48 24 0 0 58
256 51 25 1 0 59
256 52 26 0 1 59
256 54 27 0 2 58
256 56 28 0 0 60
256 59 29 1 1 61
256 60 30 0 1 63
256 62 31 0 2 61
256 64 32 0 0 63
256 67 33 1 2 62
256 68 34 0 1 64
256 70 35 0 2 63
256 72 36 0 0 65
256 75 37 1 0 66
256 76 38 0 1 66
256 78 39 0 2 65
256 80 40 0 0 67
256 83 41 1 1 68
256 84 42 0 1 69
256 86 43 0 2 67
256 88 44 0 0 69
256 91 45 1 2 68
256 92 46 0 1 70
256 94 47 0 2 69
256 96 48 0 0 71
256 99 49 1 0 72
256 100 50 0 1 72
256 102 51 0 2 71
256 104 52 0 0 73
256 107 53 1 1 74
256 108 54 0 1 75
256 110 55 0 2 73
256 112 56 0 0 75
256 115 57 1 2 74
256 116 58 0 1 76
256 118 59 0 2 75
256 120 60 0 0 77
256 123 61 1 0 78
256 124 62 0 1 78
256 126 63 0 2 76
256 128 64 0 0 80
256 131 65 1 2 77
256 132 66 0 1 80
256 134 67 0 2 78
256 136 68 0 0 82
256 139 69 1 1 81
256 140 70 0 1 82
256 142 71 0 2 81
256 144 72 0 0 83
256 147 73 1 0 84
256 148 74 0 1 83
256 150 75 0 2 82
256 152 76 0 0 86
256 155 77 1 2 84
256 156 78 0 1 85
256 158 79 0 2 85
256 160 80 0 0 88
256 163 81 1 1 87
256 164 82 0 1 88
256 166 83 0 2 87
256 168 84 0 0 89
256 171 85 1 0 90
256 172 86 0 1 89
256 174 87 0 2 88
256 176 88 0 0 92
256 179 89 1 2 90
256 180 90 0 1 91
257 0 0 0 1 15
257 2 1 0 2 21
257 4 2 0 3 42
257 6 3 0 0 50
6 进行dd文件拼接
d0=/dev/raw/raw1
d1=/dev/raw/raw2
d2=/dev/raw/raw3
d3=/dev/raw/raw4
aus=1M
# 注意虚拟extents不能跳号
# 开始使用dd拼接文件
mkdir dd_output
while read line ;do
read v_file v_ext v_vext v_cop v_dsk v_au <<< $line
eval v_d=\${d${v_dsk}};
dd if=${v_d} of=./dd_output/dbf_${v_file}.dbf bs=1M count=1 skip=${v_au} seek=${v_vext} conv=notrunc
done < fdir3.list
[grid@db2 ~]$ls -l dd_output
total 97284
-rw-r--r--. 1 grid oinstall 95420416 Dec 23 22:33 dbf_256.dbf
-rw-r--r--. 1 grid oinstall 4194304 Dec 23 22:18 dbf_257.dbf --> 257号文件也抽出来了
文件验证,坏块检查
SYS@: > select bytes/1024/1024 from v$datafile_header where file#=5;
BYTES/1024/1024
---------------
90
dbv file=dbf_256.dbf
dbv验证:128号块开始(128号extent#),出现1M坏块
[grid@db2 ~]$cat fdir3.list
256 0 0 0 0 20
256 2 1 0 3 39 --> 4号文件不是offline了吗?怎么跑这里来了 ,换成其它副本进行抽取
256 4 2 0 1 12
# 是3号磁盘(raw4)offline之后更新的数据,这里并不能识别出是坏块
# 参照fdir.list 更新fdir3.list 重新执行dd拼接或者使用单独使用dd拼接这个块
line="256 3 1 1 2 28"
read v_file v_ext v_vext v_cop v_dsk v_au <<< $line
eval v_d=\${d${v_dsk}};
dd if=${v_d} of=./dd_output/dbf_${v_file}.dbf bs=1M count=1 skip=${v_au} seek=${v_vext} conv=notrunc
# 再次进行数据文件校验
# 已经成功了,没有坏块
cd dd_output/
dbv file=dbf_256.dbf
[grid@db2 dd_output]$dbv file=dbf_256.dbf
DBVERIFY: Release 11.2.0.4.0 - Production on Fri Dec 23 22:34:42 2022
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
DBVERIFY - Verification starting : FILE = /home/grid/dd_output/dbf_256.dbf
DBVERIFY - Verification complete
Total Pages Examined : 11520
Total Pages Processed (Data) : 370
Total Pages Failing (Data) : 0
Total Pages Processed (Index): 0
Total Pages Failing (Index): 0
Total Pages Processed (Other): 141
Total Pages Processed (Seg) : 0
Total Pages Failing (Seg) : 0
Total Pages Empty : 11009
Total Pages Marked Corrupt : 0
Total Pages Influx : 0
Total Pages Encrypted : 0
Highest block SCN : 1209783 (0.1209783)
8 数据库切换文件进行使用测试验证
chown -R oracle.oinstall dd_output/
startup mount
select name from v$datafile where name like '%256%';
SYS@: > alter database rename file '+FRA/db/datafile/ts1.256.1123912897' to '/data/dd_output/dbf_256.dbf';
Database altered.
SYS@: > alter database open;
Database altered.
# 数据文件能正常使用,说明使用dd的抽取是成功的
SYS@: > select count(1) from cog.t1;
COUNT(1)
--------
25000
1 row selected.
知识总结
对asm底层原理的理解,非常有帮助,很多恢复工具恢复也是根据oracle的底层原理进行编写的