#ASM 翻译系列第三十二弹:ASM INTERNAL Find block in ASM
2017-02-23 12:10
459 查看
原文: Find block in ASM
作者: Bane Radulovic
译者:魏兴华,沃趣科技高级技术专家,主要参与公司一体机产品、监控产品、容灾产品、DBaaS平台的研发和设计。曾就职于东软集团,阿里巴巴集团,Oracle ACE组成员,DBGEEK 用户组发起人,ITPUB认证博客专家,ACOUG、SHOUG核心成员。曾在中国数据库大会、Oracle技术嘉年华、ORCL-CON、YY分享平台等公开场合多次做过数据库技术专题分享。对Oracle 并行机制、数据库异常恢复方法、ASM等有深入的研究,人称”Oracle Internal达人”,对企业数据库架构设计、故障恢复、高并发下数据库性能调优有丰富的经验,擅长从等待事件角度分析解决数据库性能问题,是OWI方法论的提倡者和践行者。
审校:魏兴华
责编:仲培艺
在本系列文章《Where is my data》中,我已经演示了如何从ASM磁盘中定位和抽取一个Oracle的block,为了让这件事做起来不那么复杂,我又写了一个perl脚本
脚本需要以Grid软件owner的身份来运行,而且要确保pert的二进制文件来自于Oracle安装软件的home目录下。在集群环境下,这个脚本可以运行在任意节点上,在运行脚本前,请检查ASM的环境变量,确定
可以以如下的方式运行脚本:
其中:filename是要抽取的块所在的文件名,对于数据文件来说,这个文件名可以从
这个脚本的输出如下:
在Exadata中是这样:
对于数据文件来说,如果文件的冗余度是external外部冗余模式,这个脚本将产生一条单一的命令,若为normal冗余,这个脚本将产生2个命令,对于high冗余,将产生3条命令。
以上我们造取了两条数据,并且定位到了数据所在的文件和BLOCK号,切换到ASM环境,注意设置正确的环境变量PERL5LIB,然后运行脚本:
执行后会将块的内容输出到文本文件
非常好,正是我们插入的数据!
我们来看一个ASM 12.1.0.1版本下的例子,是一个Exadata环境下双节点的RAC,数据文件是PDB中的一个数据文件。
和上面的例子一样,首先创建一张表然后插入一些数据:
同样获得插入数据的文件号和块号,切换到ASM的环境,然后运行perl脚本:
我们观察到,
我们将块的内容输出到了文本文件
我们注意到脚本正确计算出控制文件的block size(不同于数据块的大小8K,为16K),并且脚本产生了3个不同的命令,虽然磁盘组DATA是normal冗余,但是控制文件却做了high冗余,也就是做了三副本,控制文件在这一点上跟ASM的元数据文件一样。
如果文件是external外部冗余的,那么这个脚本将输出一个单一的命令,执行这个命令可以直接从ASM的磁盘中抽取块。
如果文件是normal冗余,这个脚本将输出2个命令,它用来从不同的磁盘中抽取块,这可能会比较有用,例如后台日志提示数据块损坏,ASM不能修复它,那么就可以通过镜像块来修复。
如果文件是high冗余,这个脚本将产生3个命令。
最后,使用这个脚本你不用知道文件的冗余度、块的大小,和任何其它属性,你只需要关心文件名和块号。
作者: Bane Radulovic
译者:魏兴华,沃趣科技高级技术专家,主要参与公司一体机产品、监控产品、容灾产品、DBaaS平台的研发和设计。曾就职于东软集团,阿里巴巴集团,Oracle ACE组成员,DBGEEK 用户组发起人,ITPUB认证博客专家,ACOUG、SHOUG核心成员。曾在中国数据库大会、Oracle技术嘉年华、ORCL-CON、YY分享平台等公开场合多次做过数据库技术专题分享。对Oracle 并行机制、数据库异常恢复方法、ASM等有深入的研究,人称”Oracle Internal达人”,对企业数据库架构设计、故障恢复、高并发下数据库性能调优有丰富的经验,擅长从等待事件角度分析解决数据库性能问题,是OWI方法论的提倡者和践行者。
审校:魏兴华
责编:仲培艺
在本系列文章《Where is my data》中,我已经演示了如何从ASM磁盘中定位和抽取一个Oracle的block,为了让这件事做起来不那么复杂,我又写了一个perl脚本
find_block.pl来简化整个操作,只需要提供数据文件的名称和需要提取的block,这个脚本就可以输出从ASM磁盘组中抽取块的命令。
find_block.pl
find_block.pl是一个perl脚本,脚本里集成了dd或kfed命令来从ASM磁盘中抽取一个块,脚本可以在Linux和Unix的ASM版本下工作,且不管是单实例还是RAC环境。(不能是FLEX ASM)
脚本需要以Grid软件owner的身份来运行,而且要确保pert的二进制文件来自于Oracle安装软件的home目录下。在集群环境下,这个脚本可以运行在任意节点上,在运行脚本前,请检查ASM的环境变量,确定
ORACLE_SID、
ORACLE_HOME、
LD_LIBRARY_PATH设定正确,而且对于10G和11GR1版本,需要设置PERL5LIB环境变量:
export PERL5LIB=$ORACLE_HOME/perl/lib/5.8.3:$ORACLE_HOME/perl/lib/site_perl
可以以如下的方式运行脚本:
$ORACLE_HOME/perl/bin/perl find_block.pl filename block
其中:filename是要抽取的块所在的文件名,对于数据文件来说,这个文件名可以从
V$DATAFILE的NAME字段获取到,block代表要从ASM抽取的块号,这个块号是数据库的块号,而不是ASM的块号。
这个脚本的输出如下:
dd if=[ASM disk path] ... of=block_N.dd
在Exadata中是这样:
kfed read dev=[ASM disk path] ... > block_N.txt
对于数据文件来说,如果文件的冗余度是external外部冗余模式,这个脚本将产生一条单一的命令,若为normal冗余,这个脚本将产生2个命令,对于high冗余,将产生3条命令。
Example with ASM version 10.2.0.1
第一个例子是单实例10.2.0.1的ASM版本,首先我在数据库中创建了一张表,插入一些数据。[oracle@cat10g ~]$ sqlplus / as sysdba SQL*Plus: Release 10.2.0.1.0 - Production on [date] SQL> create table TAB1 (name varchar2(16)) tablespace USERS; Table created. SQL> insert into TAB1 values ('CAT'); 1 row created. SQL> insert into TAB1 values ('DOG'); 1 row created. SQL> commit; Commit complete. SQL> select ROWID, NAME from TAB1; ROWID NAME ------------------ -------------------------------- AAANE+AAEAAAAGHAAA CAT AAANE+AAEAAAAGHAAB DOG SQL> select DBMS_ROWID.ROWID_BLOCK_NUMBER('AAANE+AAEAAAAGHAAA') "Block" from dual; Block --------- 391 SQL> select t.name "Tablespace", f.name "Datafile" from v$tablespace t, v$datafile f where t.ts#=f.ts# and t.name='USERS'; Tablespace Datafile ------------ -------------------------------------- USERS +DATA/cat/datafile/users.259.783204313 SQL>
以上我们造取了两条数据,并且定位到了数据所在的文件和BLOCK号,切换到ASM环境,注意设置正确的环境变量PERL5LIB,然后运行脚本:
$ export PERL5LIB=$ORACLE_HOME/perl/lib/5.8.3:$ORACLE_HOME/perl/lib/site_perl $ $ORACLE_HOME/perl/bin/perl find_block.pl +DATA/cat/datafile/users.259.783204313 391 dd if=/dev/oracleasm/disks/ASMDISK01 bs=8192 count=1 skip=100359 of=block_391.dd $
find_block.pl脚本如预期产生了输出,由于这是一个外部冗余的磁盘组,这个脚本只产生了一行dd命令的输出,我们把输出的dd命令复制后执行:
$ dd if=/dev/oracleasm/disks/ASMDISK01 bs=8192 count=1 skip=100359 of=block_391.dd $
执行后会将块的内容输出到文本文件
block_3237.dd中,然后使用操作系统的OD工具,可以看到插入表中的数据:
$ od -c block_391.dd | tail -3 0017740 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 , 001 0017760 001 003 D O G , 001 001 003 C A T 001 006 u G 0020000 $
非常好,正是我们插入的数据!
Example with ASM version 12.1.0.1 in Exadata
在Exadata中我们不能使用dd命令抽取数据块,因为ASM的磁盘对于数据库的server来说是不可见的,为了获得数据块,我们可以使用kfed工具,因此find_block.pl脚本做了这种自适应,如果是Exadata的环境,会使用kfed工具从ASM磁盘中抽取块。
我们来看一个ASM 12.1.0.1版本下的例子,是一个Exadata环境下双节点的RAC,数据文件是PDB中的一个数据文件。
和上面的例子一样,首先创建一张表然后插入一些数据:
$ sqlplus / as sysdba SQL*Plus: Release 12.1.0.1.0 Production on [date] SQL> alter pluggable database BR_PDB open; Pluggable database altered. SQL> show pdbs CON_ID CON_NAME OPEN MODE RESTRICTED ------ -------- ----------- ---------- 2 PDB$SEED READ ONLY NO ... 5 BR_PDB READ WRITE NO SQL> $ sqlplus bane/welcome1@BR_PDB SQL*Plus: Release 12.1.0.1.0 Production on [date] SQL> create table TAB1 (n number, name varchar2(16)) tablespace USERS; Table created. SQL> insert into TAB1 values (1, 'CAT'); 1 row created. SQL> insert into TAB1 values (2, 'DOG'); 1 row created. SQL> commit; Commit complete. SQL> select t.name "Tablespace", f.name "Datafile" from v$tablespace t, v$datafile f where t.ts#=f.ts# and t.name='USERS'; Tablespace Datafile ---------- --------------------------------------------- USERS +DATA/CDB/054.../DATAFILE/users.588.860861901 SQL> select ROWID, NAME from TAB1; ROWID NAME ------------------ ---- AAAWYEABfAAAACDAAA CAT AAAWYEABfAAAACDAAB DOG SQL> select DBMS_ROWID.ROWID_BLOCK_NUMBER('AAAWYEABfAAAACDAAA') "Block number" from dual; Block number ------------ 131 SQL>
同样获得插入数据的文件号和块号,切换到ASM的环境,然后运行perl脚本:
$ $ORACLE_HOME/perl/bin/perl find_block.pl +DATA/CDB/0548068A10AB14DEE053E273BB0A46D1/DATAFILE/users.588.860861901 131 kfed read dev=o/192.168.1.9/DATA_CD_03_exacelmel05 ausz=4194304 aunum=16212 blksz=8192 blknum=131 | grep -iv ^kf > block_131.txt kfed read dev=o/192.168.1.11/DATA_CD_09_exacelmel07 ausz=4194304 aunum=16267 blksz=8192 blknum=131 | grep -iv ^kf > block_131.txt
我们观察到,
find_block.pl脚本这次产生了2个命令,因此我们可以知道这是一个normal冗余的磁盘组,我们运行其中一个命令:
$ kfed read dev=o/192.168.1.9/DATA_CD_03_exacelmel05 ausz=4194304 aunum=16212 blksz=8192 blknum=131 | grep -iv ^kf > block_131.txt $
我们将块的内容输出到了文本文件
block_131.txt中,然后看到了我上面插入的数据DOG和CAT:
$ more block_131.txt ... FD5106080 00000000 00000000 ... [................] Repeat 501 times FD5107FE0 00000000 00000000 ... [........,......D] FD5107FF0 012C474F 02C10202 ... [OG,......CAT..,-] $
Find any block
find_block.pl用来从ASM磁盘组中的任何一个文件中抽取块,不仅仅是数据文件,我对控制文件和控制文件上一个随机的块运行这个脚本:
$ $ORACLE_HOME/perl/bin/perl find_block.pl +DATA/CDB/CONTROLFILE/current.289.843047837 5 kfed read dev=o/192.168.1.9/DATA_CD_10_exacelmel05 ausz=4194304 aunum=73 blksz=16384 blknum=5 | grep -iv ^kf > block_5.txt kfed read dev=o/192.168.1.11/DATA_CD_01_exacelmel07 ausz=4194304 aunum=66 blksz=16384 blknum=5 | grep -iv ^kf > block_5.txt kfed read dev=o/192.168.1.10/DATA_CD_04_exacelmel06 ausz=4194304 aunum=78 blksz=16384 blknum=5 | grep -iv ^kf > block_5.txt $
我们注意到脚本正确计算出控制文件的block size(不同于数据块的大小8K,为16K),并且脚本产生了3个不同的命令,虽然磁盘组DATA是normal冗余,但是控制文件却做了high冗余,也就是做了三副本,控制文件在这一点上跟ASM的元数据文件一样。
Conclusion
find_block.pl脚本通过dd或者kfed命令从ASM磁盘组的文件中抽取块,可能大多数情况下,我们想要从数据文件中抽取一个块,但是这个脚本不仅仅适用于数据文件,也可以从控制文件、日志文件、任何的ASM文件中抽取块。
如果文件是external外部冗余的,那么这个脚本将输出一个单一的命令,执行这个命令可以直接从ASM的磁盘中抽取块。
如果文件是normal冗余,这个脚本将输出2个命令,它用来从不同的磁盘中抽取块,这可能会比较有用,例如后台日志提示数据块损坏,ASM不能修复它,那么就可以通过镜像块来修复。
如果文件是high冗余,这个脚本将产生3个命令。
最后,使用这个脚本你不用知道文件的冗余度、块的大小,和任何其它属性,你只需要关心文件名和块号。
附脚本
#!$ORACLE_HOME/perl/bin/perl -w # # The find_block.pl constructs the command(s) to extract a block from ASM. # For a complete info about this script see ASM Support Guy blog post: # http://asmsupportguy.blogspot.com/2014/10/find-block-in-asm.html # # Copyright (C) 2014 Bane Radulovic # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or any later version. # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details # at http://www.gnu.org/licenses/. # # Version 1.00, Oct 2014 # The initial release. # # Version 1.01, Oct 2014 # Minor improvements. # # Version 1.02, Oct 2014 # Added support for AFD disks. # # Version 1.03, Nov 2014 # Added sanity checks, e.g. if the requested block is reasonable, # if the specified filename is valid, etc. # # Version 1.04, Nov 2014 # Improved the check for Exadata storage cell based disk. # use strict; use DBI; use DBD::Oracle qw(:ora_session_modes); use POSIX; # Handle the version query die "find_block.pl version 1.04\n" if ( $ARGV[0] =~ /^-v/i ); # Check the number of input arguments die "Usage: \$ORACLE_HOME/perl/bin/perl find_block.pl filename block\n" unless ( @ARGV == 2 ); # Get the filename from the first input argument my $filename = shift @ARGV; # Check if the filename makes sense. # The 'minimum' filename is +DGNAME/filename, # i.e. it has to begin with the '+' followed by a disk group name, # followed by at least one '/', followed by directory or file name... die "Error: The $filename is not a valid file name.\n" unless ( $filename =~ /^\+\w/ && $filename =~ /\/\w/ ); # Get the disk group name out of the user specified filename my $diskgroup_name = substr($filename, 1, index($filename, "/") -1 ); # Get the ASM file name out of the user specified filename my $asmfile = substr($filename, rindex($filename, "/") +1 ); # Get the block number from the second input argument my $block_number = shift @ARGV; # Check if the block number is an integer die "Usage: \$ORACLE_HOME/perl/bin/perl find_block.pl filename block\n" unless ( $block_number =~ /^\d+$/ ); # Check if the ASM SID is set die "Error: ASM SID not set.\n" unless ( $ENV{ORACLE_SID} =~ /\+ASM/ ); # Connect to the (local) ASM instance my $dbh = DBI->connect('dbi:Oracle:', "", "", { ora_session_mode => ORA_SYSDBA }) or die "$DBI::errstr\n"; # Check if the disk group exists and if it is mounted my $group_number = &asm_diskgroup("group_number", $diskgroup_name); die "Error: Disk group $diskgroup_name not mounted or does not exist.\n" unless ( $group_number ); # Check if the user specified file exists in the disk group my $file_number = &asm_alias("file_number", $asmfile, $group_number); die "Error: File $asmfile does not exist in disk group $diskgroup_name.\n" unless ( $file_number ); # Get the block size for the file my $block_size = &asm_file("block_size", $group_number, $file_number); # Get the number of blocks in the file my $file_blocks = &asm_file("blocks", $group_number, $file_number); # Check if the user specified block number makes sense die "Error: Block range for file $asmfile is: 0 - $file_blocks.\n" unless ( $block_number >= 0 && $block_number <= $file_blocks ); # Get the disk group AU size my $au_size = &asm_diskgroup("allocation_unit_size", $diskgroup_name); # Work out the blocks per AU and the virtual extent number my $blocks_per_au = $au_size/$block_size; my $xnum_kffxp = floor($block_number/$blocks_per_au); # Get the disk and AU numbers into the @disk_au array my @disk_au = &asm_kffxp($file_number, $group_number, $xnum_kffxp); die "Could not get any disk and AU numbers for file $asmfile.\n" unless ( @disk_au ); # Get the disk path(s) and generate the block extract command(s) while ( @disk_au ) { # Do not assume anything my $storage_cell = "FALSE"; # Get the disk number from @disk_au my $disk_number = shift @disk_au; # Get the AU number from @disk_au my $au_number = shift @disk_au; # Get the path for that disk number my $path = &asm_disk("path", $group_number, $disk_number); # If there is no path move to the next disk if ( ! $path ) { next; } # If ASMLIB is in use, the path will return ORCL:DISKNAME. # Set the path to /dev/oracleasm/disks/DISKNAME elsif ( $path =~ /ORCL:(.*)/ ) { $path = "/dev/oracleasm/disks/".$1; } # If ASM Filter Driver (AFD) is in use, the path will return AFD:DISKNAME. # Get the actual path from /dev/oracleafd/disks/DISKNAME elsif ( $path =~ /AFD:(.*)/ ) { if ( ! open AFDDISK, "/dev/oracleafd/disks/".$1 ) { next } else { chomp($path = <AFDDISK>) } } # For Exadata storage cell based disk, the path will start with o/IP address elsif ( $path =~ /^o\/\d{1,3}\./ ) { $storage_cell = "TRUE"; } if ( $storage_cell eq "TRUE" ) { # Construct the kfed command for Exadata storage cell based disk # dev=$path ausz=$au_size aunum=$au_number blksz=$block_size blknum=$block_number # The grep filters out the kfed stuff print "kfed read dev=$path ausz=$au_size aunum=$au_number blksz=$block_size blknum=$block_number | grep -iv ^kf > block_$block_number.txt\n"; } else { # Construct the dd command # if=$path bs=$block_size count=1 skip=$skip of=block_$block_number.dd my $skip=$au_number*$blocks_per_au + $block_number%$blocks_per_au; print "dd if=$path bs=$block_size count=1 skip=$skip of=block_$block_number.dd\n"; } } # We are done. Disconnect from the (local) ASM instance $dbh->disconnect; # Subs # Get a column from v$asm_file for a given group number and file number sub asm_file { my $col = shift @_; my $group_number = shift @_; my $file_number = shift @_; my $sql = $dbh->prepare("select $col from v\$asm_file where group_number=$group_number and file_number=$file_number"); $sql->execute; my $col_value = $sql->fetchrow_array; $sql->finish; return $col_value; } # Get a column from v$asm_alias for a given (file) name and group number sub asm_alias { my $col = shift @_; my $name = shift @_; my $group_number = shift @_; my $sql = $dbh->prepare("select $col from v\$asm_alias where lower(name)=lower('$name') and group_number=$group_number"); $sql->execute; my $col_value = $sql->fetchrow_array; $sql->finish; return $col_value; } # Get a column from v$asm_diskgroup for a given disk group name sub asm_diskgroup { my $col = shift @_; my $name = shift @_; my $sql = $dbh->prepare("select $col from v\$asm_diskgroup where name=upper('$name')"); $sql->execute; my $col_value = $sql->fetchrow_array; $sql->finish; return $col_value; } # Get a column from v$asm_disk for a given group number and disk number sub asm_disk { my $col = shift @_; my $group_number = shift @_; my $disk_number = shift @_; my $sql = $dbh->prepare("select $col from v\$asm_disk where group_number=$group_number and disk_number=$disk_number"); $sql->execute; my $col_value = $sql->fetchrow_array; $sql->finish; return $col_value; } # Get the disk and AU numbers from x$kffxp for a given virtual extent number. # This will return one row for an external redundancy file, # two rows for a normal redundancy and three rows for a high redundancy. # Well, it will return an array with disk and AU pairs, not rows. sub asm_kffxp { my $file_number = shift @_; my $group_number = shift @_; my $xnum = shift @_; # The @disk_au array to hold the disk number, AU number rows my @disk_au; my $sql = $dbh->prepare("select disk_kffxp, au_kffxp from x\$kffxp where number_kffxp=$file_number and group_kffxp=$group_number and xnum_kffxp=$xnum"); $sql->execute; # Expecting one disk number and one AU number per row while ( my @row = $sql->fetchrow_array) { # Add each (element of the) row to @disk_au array foreach ( @row ) { push @disk_au, $_ } } $sql->finish; return @disk_au; }
相关文章推荐
- #ASM 翻译系列第二十七弹:ASM INTERNAL ASM METADATA BLOCK
- #ASM 翻译系列第二十九弹:ASM INTERNAL Free Space Table
- #ASM 翻译系列第二十四弹:ASM Internal ASM files number 10 and 11
- #ASM 翻译系列第二十弹:ASM Internal ASM file number 7
- #ASM 翻译系列第三十一弹:ASM INTERNAL How many allocation units per file
- #ASM 翻译系列第二十三弹:ASM Internal ASM files number 12 and 254
- #ASM 翻译系列第二十二弹:ASM Internal ASM file number 8
- #ASM 翻译系列第十九弹:ASM Internal ASM Continuing Operations Directory
- #ASM 翻译系列第十七弹:ASM Internal ASM Disk Directory
- #ASM 翻译系列第三十四弹:ASM INTERNAL ASM Disk Group Attributes
- #ASM 翻译系列第十六弹:ASM Internal ASM Active Change Directory
- #ASM 翻译系列第二十一弹:ASM Attributes Directory
- ASM 翻译系列第十四弹:ASM Internal Rebalancing act
- ASM 翻译系列第八弹:ASM Internal ASM file extent map
- #ASM 翻译系列第三十三弹:ASM 高级知识 REQUIRED_MIRROR_FREE_MB
- #ASM 翻译系列第十八弹:ASM Internal ASM file number 5
- #ASM 翻译系列第二十五弹:ASM 高级知识 When will my rebalance complete
- ASM 翻译系列第十二弹:ASM Internal amdu - ASM Metadata Dump Utility
- ASM 翻译系列第十七弹:ASM Internal ASM Disk Directory
- ###ASM 翻译系列第二十八弹:ASM INTERNAL Partnership and Status Table