ASM 翻译系列第三十二弹:自制数据抽取小工具

Find block in ASM

在本系列文章【 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的身份来运行,而且要确保perl的二进制文件来自于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的块号。

对于数据文件来说,如果文件的冗余度是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

ASM空间的占用取决于2个因素:文件的实际大小和磁盘组的冗余度。

在external冗余的磁盘组中,空间的占用:文件实际大小+1个AU(文件头)+1个额外的AU(如果文件大于60个AU)。

在一个normal冗余的磁盘组中,空间的占用:两倍的文件实际大小+2个AU(文件头)+3个额外的AU(如果文件大于60个AU)

在一个high冗余的磁盘组中,空间的占用:三倍的文件实际大小+3个AU(文件头)+3个额外的AU(如果文件大于60个AU)

在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;

 }


本文来自合作伙伴“DBGEEK”

时间: 2024-09-17 04:40:34

ASM 翻译系列第三十二弹:自制数据抽取小工具的相关文章

ASM 翻译系列第三十四弹:ASM磁盘组重要属性介绍

ASM Disk Group Attributes 磁盘组的属性是ASM 11.1版本引入的,是磁盘组层面而非ASM实例层面的.磁盘组的属性有一些只能在创建磁盘组时指定,有一些只能在创建之后指定,还有一些可以在任何时候指定. 本篇内容是对本系列文章-[ASM Attributes Directory]的展开. ACCESS_CONTROL.ENABLED ACCESS_CONTROL.ENABLED属性指定了一个磁盘组的ASM File Access Control是否启用,参数的值可以设置为t

ASM 翻译系列第三十五弹:ASM 253号文件——ASM spfile

ASM spfile in a disk group 从ASM版本11.2开始,ASM spfile可以储存在ASM磁盘组里.事实上,在安装ASM时,OUI就已经把ASM spfile放在了磁盘组中.对于单实例环境和集群环境都是这样.在安装过程中创建的第一个磁盘组是spfile的默认位置,但这不是必要的.ASM spfile还是可以放在文件系统上,就是$ORACLE_HOME/dbs目录下. New ASMCMD commands 为支持该特性,ASMCMD引入了新的命令用来备份,复制和移动AS

Oracle ASM 翻译系列第三十弹:高级知识 Physical metadata replication

Physical metadata replication 从版本12.1开始,ASM会对某些物理元数据做一份复制,具体的说是每个磁盘的第一个AU(0号AU)上元数据.这意味着,ASM同时维护着两份磁盘头.FST(Free Space Table)表.AT(Allocation table)表的数据.需要注意的是ASM对这些数据采用的是复制(replicate),而不是镜像(mirror).ASM镜像(mirror)意味着把一份数据,拷贝到不同磁盘上:而物理元数据的副本位于相同的磁盘,因此使用的

ASM 翻译系列第三弹:基础知识 About ASM disk groups, disks and files

Oracle ASM使用磁盘组来存放数据文件,每一个ASM的磁盘组由一些ASM磁盘组成,每一个ASM磁盘组本身是一个独立的存储单元,是自描述的,对于ASM磁盘组中数据库文件,ASM提供一个文件系统的接口,方便DBA做管理.存放在ASM磁盘组中的文件被均匀的分布在磁盘组中的所有磁盘上,通过这种方式,每一块磁盘都可以提供一致的性能,同时ASM的性能可以比得上裸设备的性能.[摘录自11GR2版本的ASM官方文档] ASM Disk Groups 一个ASM磁盘组是由一个或多个ASM磁盘组成的,每个AS

ASM 翻译系列第三十三弹:REQUIRED_MIRROR_FREE_MB的含义

REQUIRED_MIRROR_FREE_MB REQUIRED_MIRROR_FREE_MB和USABLE_FILE_MB是V$ASM_DISKGROUP[_STAT]视图中非常有趣的两列.Oracle Support部门收到的很多问题是关于这两列的意义以及它们的值是怎么计算的.我本打算写些文章介绍一下,但是我意识到我不可能比Harald van Breederode做的更出色.因此我征得了他的同意来直接参考他的文章,所以还是请欣赏他的大作吧. https://prutser.wordpres

ASM 翻译系列第三十一弹:了解ASM文件的空间分配

How many allocation units per file 本文主要是对ASM文件的空间分配进行一些探讨和研究. ASM空间分配的最小单位是AU,默认的AU size是1MB,但在Exadata下AU 的默认大小是4MB. ASM文件的空间分配是以extent为单位,每一个extent是由一个或多个AU组成,在11.2版本,前20000个extent,每一个extent由1个AU组成,接下来的20000个extent,每一个由4个AU组成,再超出的extent,每一个由16个AU组成.

C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(三十二)

C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(三十二) 雷.混.冰.毒.火.风 - 幻化中的魔法魅力 本节,我将为大家演示如何为游戏中的魔法增加华丽的附加属性. 第一步,定义规则: 1)定义魔法附加属性分类:在本教程示例游戏中,我将魔法附加属性定义为6类:雷.混.冰.毒.火.风,为什么要以这样无规律的方式去命名?因为是教程,我们需要学习的是如何实现对应效果,此6类属性算是目前网游中最流行的六大魔法属性,如果大家都掌握了,无论是中国式5行还是诸如其他的风格设

微信小程序把玩(三十二)Image API

原文:微信小程序把玩(三十二)Image API 选择图片时可设置图片是否是原图,图片来源.这用的也挺常见的,比如个人中心中设置头像,可以与wx.upLoadFile()API使用 主要方法: wx.chooseImage(object) wxml <!--监听按钮--> <button type="primary" bindtap="listenerButtonChooseImage">点击我选择相册</button> <

ASP 三十二条精华代码

欢迎您访问我在博客园上的博客,上面有更多相关技术文章.我的博客地址是:http://www.cnblogs.com/scq2099yt. 欢迎您访问我在百度上的博客,上面有更多相关技术文章.我的博客地址是:http://hi.baidu.com/scq2099yt. 整理收藏: ASP 三十二条精华代码   1. oncontextmenu="window.event.returnvalue=false" 将彻底屏蔽鼠标右键 <table border oncontextmenu