揭密Oracle之七种武器二:DTrace语法:跟踪物理IO

[i=s] 本帖最后由 vage 于 2012-5-7 14:02 编辑

揭密Oracle之七种武器二:DTrace语法:跟踪物理IO

        其实本章是以物理IO为例,介绍DTrace的基本语法。本篇内容还是比较多的,应该足够有兴趣的朋友学习一周了。下周,再推出我的第三章。
         另外说明一下,我并不是DTrace专家,我用DTrace主要目的是研究Oracle。我们不会对DTrace作过多深入的介绍(不过DTrace本身也没有深入的东西,调试工具吗,复杂的是要调试的目标),我们的主要内容,还是逐步深入,介绍如何用DTrace分析、研究Oracle方法。
        授人以鱼,不如授人以渔。我相信,方法比结果重要。

        本篇内容会用到一本书,Solaris 动态跟踪指南,可以到这里下载。

D语言全面介绍.pdf
(2012-05-07 14:00:29, Size: 1.83 MB, Downloads: )

这个是Solaris官方提供的文档,而且是中文的。当字典来查,非常不错。


第二章  DTrace语法:跟踪物理IO


第一节  Hello World

        DTrace是什么,这个问题很容易回答:调试工具。具体我们可以这样理解,Solaris在其内部,增加了N多的触发器,这些触发器平时是Disable的,对性能没有任
何影响。你可以通过DTrace,Enable某些触发器。并且,在Enable的同时,还可以为这些触发器指定动作。
        比如,有一个I/O触发器,你用DTrace Enable了它,同时,你定义动作,“每次发生I/O时,显示出I/O大小”。当然,还可以定义更复杂的动作,显示I/O的内容
、甚至修改I/O数据。进程想往磁盘中写个A,你可以用DTrace,将A换成B。当然,我们调试进程,一般不需要修改,只需要观察。
        换成DTrace中的术语,触发器就是Probe,可以译为探针。探针并时都是关闭的,也就是Disable的。我们可以使用DTrace,打开探针,并为探针指定动作。当探针
被触发,你通过DTrace为探针定义的动作,就会被执行。
        好,闲言少叙,试试我们的第一个DTrace程序吧。曾几何时,无论我们学习什么语言,第一个程序总是“Hello World”,让我们也来个DTrace版的“Hello World
”吧。
        在root下,vi test1.d,输入如下命令:
BEGIN
{
    printf("hello world, www.MythData.com " ;
    exit(0);
}
如下执行此脚本:
# dtrace -s test1.d
结果显示:
dtrace: script. 'test1.d' matched 1 probe
CPU     ID                    FUNCTION:NAME
  3      1                           :BEGIN hello world, www.MythData.com
        在此加上我的BLOG地址,我的那篇突然35岁,虽然被转载的到处都是,但被截掉了我的BLOG地址。唉。如果有人转载的话,应该也会有人截掉的。
        闲言少叙,上面就是显示结果了。对于我的程序和输出结果,略加说明。
        1、BEGIN:它是DTrace的探针之一。也是最简单的探针。它不和操作系统任何操作关联,一般它用来做DTrace程序运行的初始化工作。BEGIN探针中的代码,会在
DTrace程序开始时运行。
        2、大括号:如我们所见,探针名之下,就是大括号。这足以说明DTrace的设计者是C语言迷,将C语言的格式带入到了DTrace中来。
        3、大括号中间的语句:这就是我们为BEGIN探针定义的动作了。包含两条语句,显示和退出。每条语句之后以;号结尾。
        4、关于这两条语句,我就不再多说了,printf,在此的使用方法,完全和C语言一样。
        5、两注意事项,(1)、大小写是敏感的。(2)、如果不加exit(0)的话,此程序运行完将不会退出。可以手工Ctrl+C退出。
        输出结果的话:
        1、CPU列为3,说明此DTrace程序在运行时,刚好在3号CPU上执行命令。
        2、ID列是探针编号。
        3、FUNCTION:NAME,:BEGIN,探针名相关信息,这个后面再详细说。
        4、最后无列名的部分,hello world, www.MythData.com,就是我们程序的输出结果了。
        最后,每次运行此程序时,都要dtrace -s,太麻繁了。我们可以添加一个#!/usr/sbin/dtrace -s在程序头,如下所示:
#!/usr/sbin/dtrace -s
BEGIN
{
    printf("hello world, www.MythData.com " ;
    exit(0);
}
        保存,使用chmod 755 test1.d,赋上去可执行权限,如下方式执行:
# ./test1.d
dtrace: script. './test1.d' matched 1 probe
CPU     ID                    FUNCTION:NAME
  0      1                           :BEGIN hello world, www.MythData.com

第二节  详述探针(Probe)


        完整的探针描述符,绝对不至上节我们遇到的BEGIN这么简单。它包括PROVIDER(提供器),MODULE(模块名),FUNCTION(函数名)和NAME(探针名称)四部分
。BEGIN只是最简单的一个特例。
        PROVIDER是最上层的称号,比如有IO PROVIDER,进程PROVIDER,等等。每种PROVIDER根据其包含的探针不同,又分为N种MODULE。MODULE之中又包含各种FUNCTION
,最后的NAME是探针名,通常是进入、开始、退出、完成这些东西,在进入一个FUNCTION(函数)、退出函数、完成函数等等动作发生时被触发。
        我们以IO为例,这应该是我们最关心的话题。操作系统中大部分IO事件的开始处,有这样一个探针:
io:genunix:bdev_strategy:start
        io是PROVIDER,genunix是MODULE。bdev_strategy是FUNCTION,所有串行磁盘I/O事件将调用bdev_strategy函数完成。最后一个,start,bdev_strategy函数入口
处的探针。
        我们可以这样称呼它,io提供器下的genunix模块中的bdev_strategy函数上的start探针。我们可以理解为某数据库下某Schema中的某个表上的某某触发器。一个
探针的称呼其实无所谓。了解Solaris一供为我们提供了什么PROVIDER(提供器),这些提供器下都有什么MODULE(模块),这些模块中都有什么FUNCTION(函数),以及
这些函数上都有什么探针,这才是重要的。关于这点,我们可以参考《Solaris 动态跟踪指南》,这是本书像一本字典,详细介绍了所有的提供器、模块等等。
        好,先以io:genunix:bdev_strategy:start为例子,测试一下吧:
vi test2.d
#!/usr/sbin/dtrace -s
BEGIN
{
    i=0;
}
io:genunix:bdev_strategy:start
{
    printf("%d number physio IO",i);
    i++;
}
保存,chmod 755 test2.d,这是执行的结果:
# ./test2.d
dtrace: script. './test2.d' matched 2 probes
CPU     ID                    FUNCTION:NAME
  1    781              bdev_strategy:start 0 number physio IO
  1    781              bdev_strategy:start 1 number physio IO
  1    781              bdev_strategy:start 2 number physio IO
  1    781              bdev_strategy:start 3 number physio IO
  1    781              bdev_strategy:start 4 number physio IO
  1    781              bdev_strategy:start 5 number physio IO
……………………
        每有一次IO,程序会都会显示一行,“  1    781              bdev_strategy:star”,这一部分是固定输出,这一部分其实可以用一个参数关掉。参数我们以
后再说。后面“ 0 number physio IO”,是我们程序的输出结果。
        如果我们不按Ctrl+C,程序会一直显示下去。每有一次串行IO发生,准确说是每调用一次bdev_strategy函数,探针被触发一次,就会显示一行。
        只显示IO的次数,也没啥意义。其实我们可以显示更多的东西。但要对IO类探针进一步了解些。
        bdev_strategy既然被叫作函数,是函数的话,当然有参数。它一共有3个参数,参数1是bufinfo_t型的结构,参数2是devinfo_t型结构,参数3是fileinfo_t型结
构。可以参见《Solaris 动态跟踪指南》 356页。
        另外,结构,Struct,C语言的基本东西。不会的话,去看看潭浩强的C语言吧。二级C语言,我想我们都应该没啥问题吧。确定写DTrace脚本,连二级C都不需要,
只需要对C语言有最基本的了解即可。
        这三个结构当中,fileinfo_t包含的有I/O所针对的文件名,请允许我粘一段《Solaris 动态跟踪指南》 359页的内容,fileinfo_t结构的定义:
typedef struct fileinfo
{
        string fi_name;         /* name (basename of fi_pathname) */
        string fi_dirname;         /* directory (dirname of fi_pathname) */
        string fi_pathname;         /* full pathname */
        offset_t fi_offset;         /* offset within file */
        string fi_fs;                 /* filesystem */
        string fi_mount;         /* mount point of file system */
} fileinfo_t;
        在此,照顾一下不会C语言的人,简单说明一下,如果我们要访问结构中的内容,格式是“结构名.域”,或者“结构指针->域”。在DTrace中,我们得到的一般都
是指针。
        好,下面我们改一下脚本程序:
io:genunix:bdev_strategy:start
{
    printf("%s",args[2]->fi_pathname);
}
        args[2],是bdev_strategy函数的第三个参数,这是Dtrace中的固定用法。DTrace中还会有一些类似的固定用法,可以参考《Solaris 动态跟踪指南》P68页,内
置变量。我们以后还会用到一些其他的。
        在bdev_strategy函数中,第三个参数是fileinfo_t型的指针,也就是说,我们可以用“args[2]->域”的格式,访问fileinfo_t型结构中的域。我们此外访问的域
是fi_pathname,也就是文件的完整路径加名字,形式就是如上面所示:args[2]->fi_pathname。
这是我执行后的结果:
# ./test2.d
dtrace: script. './test2.d' matched 2 probes
CPU     ID                    FUNCTION:NAME
  2    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl
  2    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl
  0    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl
  0    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl
  1    781              bdev_strategy:start
  1    781              bdev_strategy:start
  1    781              bdev_strategy:start
  1    781              bdev_strategy:start
  1    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl
  1    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl
  1    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl
        可以看到,有很多控制文件的写。随带说一下,我这个测试库很闲,没有任何操作。但你可以看着表统计一下,不超过3秒,肯定会有一次控制文件的IO操作。原
因是什么,我就不用再说了吧。
        再进一步的,Oracle每次控制文件的IO是多大呢? IO的大小在bufinfo_t结构中的b_bcount域,你可以查看《Solaris 动态跟踪指南》 356页,为了节省篇幅,我
就不再粘过来了。bufinfo_t结构的指针,是bdev_strategy的第一个参数,也就是args[0]。因此,我们可以如下再次修改代码:

io:genunix:bdev_strategy:start
{
    printf("%s %d",args[2]->fi_pathname,args[0]->b_bcount);
}
这是我的执行结果:
  1    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl 8192
  2    781              bdev_strategy:start /var/tmp/Exwla4xc 8192
  2    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/redo03 4096
  1    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl 8192
  1    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl 8192
  1    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl 8192
        控制文件的IO大小,很整齐的都是8192字节,8K,控制文件的块大小。出乎我意料的是,有一个Redo文件:redo03,它的IO大小是4096。我以前一直以为,应该是
512才对,因为Solaris下,Redo的块大小就是512字节啊(我碰到的系统,好像只有HP的不是512)。
        不急,我们还没有搞清楚这些IO是读还是写呢,说不定是归档的读Redo IO呢。还是bufinfo_t结构,b_flags域,说明了IO类型。关于这个域,在操作系统内部定
义了几个标志(就是用#define 定义的),B_WRITE代表IO是写,B_READ代表是读,还有些其他的,自己到357页查吧。
        我将代码修改如下,添加上去IO类别的判断:
io:genunix:bdev_strategy:start
{
    printf("%s %d %s",args[2]->fi_pathname,args[0]->b_bcount,args[0]->b_flags&B_READ?"R":"W" );
}
        args[0]->b_flags&B_READ?"R":"W",这种使用形式,条件表达式,是我们以后常用的形式,因为DTrace中没有if、while等流程控制语句,所以条件表达式将是if
的常用替代者,但它必竟替代不了复杂的控制语句。
        仍然是为了程序不太好的人,介绍一下这个条件表达式:“条件?值1?值2”,将条件为True,值1为整个条件表达式的值。否则,值2为整个条件表达式的值。
这是执行结果:
  3    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl 8192 W
  3    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl 8192 W
  2    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/redo01 4096 W
  1    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl 8192 W
  1    781              bdev_strategy:start /export/home/oracle/opt/dbdata/h1/control01.ctl 8192 W
………………
        控制文件IO也全是写,还有,中间的Redo文件IO,是我专门修改1行,Commit一下产生的,IO大小是4096字节,而且是写IO。看来,的确是LGWR在写Redo01。我们
可以观察一会儿,不会发现Redo文件有512字节IO,这也说明了一个我之前一直忽视的问题,先卖个关子,我们待会再讨论。
        
        我们为io:genunix:bdev_strategy:start处的探针定义了动作,当运行此DTrace脚本时,我们就开启了探针io:genunix:bdev_strategy:start。但有些IO,并不是
通过bdev_strategy函数完成的,探针io:genunix:bdev_strategy:start捕获不得这些IO。为了开启更多的探针、捕获更多的IO操作,在完整的探针描述符中,我们可以确
实部分内容,下面,如下修改程序:
io:genunix::start
{
    printf("%s %d %s",args[2]->fi_pathname,args[0]->b_bcount,args[0]->b_flags&B_READ?"R":"W" );
}
脚本程序运行后的提示:
# ./test2.d
dtrace: script. './test2.d' matched 4 probes
CPU     ID                    FUNCTION:NAME
  2    782              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/control01.ctl 8192 W
  2    782              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/control01.ctl 8192 W
  2    782              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/control01.ctl 8192 W
        其中,第一行“dtrace: script. './test2.d' matched 4 probes”,说明一共开启了4个探针,比之前的测试,多开启了两个探针。
        我们还可以进一步省略,io:::start,这将开启7个探针。我就不再测试了。但我们不能写成:::start,或io:::这样的形式。
        我们还可以使用通配符,如“i*:::start”,这就是打开所有i开头的提供器中的所有模块、所有函数的Start探针。当然,我们也可以在模块、函数名中,使用通
配符,但不能在探针名中使用通配符。比如,这样将是错误的:“io:::st*”。通配符还可以是问号,比如:“i?:::start”。 * 号代表所有字符,一个 ? 号,只能代表
一个字符。
        探针的使用,说的也就差不多了,最后再来一个总结,我们如何知道Solaris有哪些探针,当然,我们可以查看《Solaris 动态跟踪指南》。除了这个之外,
dtrace -l 命令可以查看所有的探针:
# dtrace -l|wc -l
   51805
        我使用的Solaris中,一共有5万多个探针。
        我们还可以显示某一个提供器下所有探针,这样更有针对性,比如,显示io提供器下有什么模块、函数、探针:
# dtrace -lP io
   ID   PROVIDER            MODULE                          FUNCTION NAME
  767         io           genunix                           biodone done
  768         io           genunix                           biowait wait-done
  769         io           genunix                           biowait wait-start
  780         io           genunix                    default_physio start
  781         io           genunix                     bdev_strategy start
  782         io           genunix                           aphysio start
2530         io               nfs                          nfs4_bio done
2531         io               nfs                          nfs3_bio done
2532         io               nfs                           nfs_bio done
2533         io               nfs                          nfs4_bio start
2534         io               nfs                          nfs3_bio start
2535         io               nfs                           nfs_bio start
        不多,io提供器下,只有13个探针。
        好了,探针的内容我们就说到这儿,下面,下一项内容:谓词。

第三节 谓词


        谓词其实就是条件语句,减化版的条件语句。比如,我们继续前面的例子,我只想观察一下针对Redo文件的IO,我们可以这样做,先用如下命令确认一下当前Redo
文件是谁:
set linesize 10000
select * from v$log;
    GROUP#    THREAD#  SEQUENCE#      BYTES    MEMBERS ARC STATUS           FIRST_CHANGE# FIRST_TIM
---------- ---------- ---------- ---------- ---------- --- ---------------- ------------- ---------
         1          1        470   62914560          1 YES ACTIVE                 9387236 05-MAY-12
         2          1        471   62914560          1 NO  CURRENT                9388037 05-MAY-12
         3          1        469   62914560          1 YES INACTIVE               9386469 05-MAY-12
2号Redo文件是当前Redo文件,再如下确认一下2号Redo文件是谁:
select member from v$logfile where group#=2;
MEMBER
------------------------------------------------------------------------------------------------------------------------------------------------------
/export/home/oracle/opt/dbdata/h1/redo2.log
        好,如下修改脚本:
io:::start
/ args[2]->fi_pathname=="/export/home/oracle/opt/dbdata/h1/redo2.log" /
{
        printf("%s %s %d %s\n",args[1]->dev_statname,args[2]->fi_pathname,args[0]->b_bcount,args[0]->b_flags&B_READ?"R":"W" );
}
        探针描述符我简写为:io:::start,我开启了所有IO开始处的探针,拦截io提供器下所有的IO函数调用。在探针描述符之下的 / ………… / ,就是本节所要介绍
的谓词。简单点说,就是条件。当探针被触发后,只有满足谓词条件,才会执行探针下的动作。
        结合上面的程序,也就是当有IO发生时,只有当IO是针对redo2.log这个文件的,才会执行下面的Printf命令。
        开一个数据库会话,使用update命令更新一行,观察一下行针对Redo的IO。
        我针对一个很小的表,执行如下命令:
SQL> update a1 set id=id+0 where id=1;
1 row updated.
SQL> commit;
Commit complete.
        观察结果, update a1 set id=id+0 where id=1时,并没有Redo的IO,这是因为在全新的IMU模式下,Redo机制Oracle做了很大调整,DML语句执行时,只要没有
超过private strand大小,日志不会被写进Log Buffer。关于这些内容,后面我们专门开个帖子详细讨论吧。在Commit时,就有了如下输出:
# ./test2.d
dtrace: script. './test2.d' matched 7 probes
CPU     ID                    FUNCTION:NAME
  2    782              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/redo2.log 4096 W
        有4096字节的针对Redo2.log的写IO。但是,注意,并不是每次提交时,都会有IO。下面是我做的一个完整测试,我一共执行了上面的Update、commit命令共10次
,有6次commit时有IO:
# ./test2.d
dtrace: script. './test2.d' matched 7 probes
CPU     ID                    FUNCTION:NAME
  2    782              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/redo3.log 4096 W
  2    782              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/redo3.log 4096 W
  3    782              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/redo3.log 4096 W
  1    782              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/redo3.log 4096 W
  3    782              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/redo3.log 4096 W
  2    782              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/redo3.log 4096 W
        我们可以用truss 跟踪一下lgwr进程,每次提交时,lgwr都会调用pwrite64函数,写Redo文件。写的大小不等,最小时会是512字节。而且,每次提交时,都会写
Redo文件。这就是Oracle的运行法则之一吗,提交时,一定会写Redo文件。但是,如果你使用的是文件系统,对日志文件的写,操作系统有可能只是暂时先写进主机的缓存
当中。
        当然,在Solaris下,我们可以mount -o directio 这样Mount磁盘,所有IO都将是直接IO了。
        使用文件系统是危险的。我只了解Oracle,不了解各种文件系统。严谨点的说法,使用某些文件系统存放Redo文件是危险的。有可能造成提交后的数据丢失。如果
你决定使用文件系统,一定要详细了解下你的文件系统,针对Redo文件,是否是直接IO,或者在突然当机后的处理,有没有类似Oracle的实例恢复机制。
        我们可以测试一下,做一个DML操作,提交,然后马上关掉主机电源(我是Power Off掉虚拟机的电源),再重新启动虚拟机、数据库,查看刚才已经提交DML的结
果,你会发现,有时DML的结果会丢失掉的。注意,是已提交的DML结果会丢失。
        在谓词中,我们可以使用常见的||、&&、!,就是或、与、非。比如,我共有三个日志文件,上面的谓词我捕获一个日志文件redo2.log的IO,为了三个文件都捕
获,可以如下改写:
io:::start
/ args[2]->fi_pathname=="/export/home/oracle/opt/dbdata/h1/redo1.log" ||
  args[2]->fi_pathname=="/export/home/oracle/opt/dbdata/h1/redo2.log" ||
  args[2]->fi_pathname=="/export/home/oracle/opt/dbdata/h1/redo3.log" /
{
        printf("%s %s %d %s\n",args[1]->dev_statname,args[2]->fi_pathname,args[0]->b_bcount,args[0]->b_flags&B_READ?"R":"W" );
}
        谓词中我使用两个||符号,也就是“或”,将三个条件连接起来。只要有任意一个条件满足,都会执行下面的printf命令。这样,无论日志怎么切换,我们都可以
观察Redo的IO情况了。
        那么,ASM会不会有这样的情况呢,我们可以测试下。
        首先,配置ASM。我们先在10G下测试吧,10G的ASM配置比较简单,网上资料也比较多。11GR2的,我们以后再配置、观察。
(1)、配置ASM
# export ORACLE_HOME=/export/home/oracle/opt//product/10.2.0/
# cd $ORACLE_HOME/bin
# ./localconfig add
(2)、准备ASM参数文件
$ cat asmpfile.ora
instance_type=asm
cluster_database=false
asm_diskgroups=dg1
asm_diskstring='/dev/rdsk/c*'
processes=200
remote_login_passwordfile=exclusive
core_dump_dest=/export/home/oracle/opt/admin/myasm/cdump
user_dump_dest=/export/home/oracle/opt/admin/myasm/udump
background_dump_dest=/export/home/oracle/opt/admin/myasm/bdump
(3)、启动ASM实例
        先要保证参数文件中background_dump_dest几个参数中所指定的位置是有的,如下启动ASM实例即可:
ORACLE_SID=myasm
sqlplus / as sysdba
create spfile from pfile='/export/home/oracle/asmpfile.ora';
startup nomount;
        在我博客上有一篇文章,专门讲如何配置ASM的: http://space.itpub.net/321157/viewspace-718805
(4)、给虚拟机添加新的磁盘。
        这个我就不多说了,要先关掉虚拟机,添加完磁盘再重启。先添加两块吧,大小不用太大。
(5)、格式化
        格式化的步骤比较长,我写的详细些:
# format    Searching for disks...done
AVAILABLE DISK SELECTIONS:
       0. c0d1
          [url=]/pci@0,0/pci-ide@7,1/ide@1/cmdk@1,0[/url]
       1. c1t1d0
          [url=]/pci@0,0/pci15ad,1976@10/sd@1,0[/url]
       2. c1t3d0
          [url=]/pci@0,0/pci15ad,1976@10/sd@3,0[/url]
       3. c2t0d0
          [url=]/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@0,0[/url]
       4. c2t1d0
          [url=]/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0[/url]
Specify disk (enter its number):1    c1t1d0。
selecting c1t1d0
[disk formatted]

FORMAT MENU:
        disk       - select a disk
        type       - select (define) a disk type
        partition  - select (define) a partition table
        current    - describe the current disk
        format     - format and analyze the disk
        fdisk      - run the fdisk program
        repair     - repair a defective sector
        label      - write label to the disk
        analyze    - surface analysis
        defect     - defect list management
        backup     - search for backup labels
        verify     - read and display labels
        save       - save new disk/partition definitions
        inquiry    - show vendor, product and revision
        volname    - set 8-character volume name
        !     - execute , then return
        quit
format> fdisk    No fdisk table exists. The default partition for the disk is:
  a 100% "SOLARIS System" partition
Type "y" to accept the default partition,  otherwise type "n" to edit the
partition table.
y format> partition    PARTITION MENU:
        0      - change `0' partition
        1      - change `1' partition
        2      - change `2' partition
        3      - change `3' partition
        4      - change `4' partition
        5      - change `5' partition
        6      - change `6' partition
        7      - change `7' partition
        select - select a predefined table
        modify - modify a predefined partition table
        name   - name the current table
        print  - display the current table
        label  - write partition map and label to the disk
        ! - execute , then return
        quit
partition> print       Current partition table (original):
Total disk cylinders available: 98 + 2 (reserved cylinders)
Part      Tag    Flag     Cylinders      Size            Blocks
  0 unassigned    wm       0             0         (0/0/0)       0
  1 unassigned    wm       0             0         (0/0/0)       0
  2     backup    wu       0 - 97       98.00MB    (98/0/0) 200704     3 unassigned    wm       0             0         (0/0/0)       0
  4 unassigned    wm       0             0         (0/0/0)       0
  5 unassigned    wm       0             0         (0/0/0)       0
  6 unassigned    wm       0             0         (0/0/0)       0
  7 unassigned    wm       0             0         (0/0/0)       0
  8       boot    wu       0 -  0        1.00MB    (1/0/0)    2048     9 unassigned    wm       0             0         (0/0/0)       0
partition> 0    Part      Tag    Flag     Cylinders      Size            Blocks
  0 unassigned    wm       0             0         (0/0/0)       0
Enter partition id tag[unassigned]:    Enter partition permission flags[wm]:   Enter new starting cyl[0]:1             Enter partition size[0b, 0c, 0e, 0.00mb, 0.00gb]: 95mb   partition> label    Ready to label disk, continue? y   partition> quit    FORMAT MENU:
        disk       - select a disk
        type       - select (define) a disk type
        partition  - select (define) a partition table
        current    - describe the current disk
        format     - format and analyze the disk
        fdisk      - run the fdisk program
        repair     - repair a defective sector
        label      - write label to the disk
        analyze    - surface analysis
        defect     - defect list management
        backup     - search for backup labels
        verify     - read and display labels
        save       - save new disk/partition definitions
        inquiry    - show vendor, product and revision
        volname    - set 8-character volume name
        !     - execute , then return
        quit
format> type          AVAILABLE DRIVE TYPES:
        0. Auto configure
        1. DEFAULT
        2. other
Specify disk type (enter its number)[1]: 1    selecting c1t1d0
[disk formatted]
format> save                   Saving new disk and partition definitions
Enter file name["./format.dat"]:         format> quit             #
        然后到刚才启动的ASM实例中,确定asm_diskstring参数的值为/dev/rdsk/c*:
SQL> show parameter string
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
asm_diskstring                       string      /dev/rdsk/c*
        查看我们刚才Format的磁盘:
SQL> select path from v$asm_disk;
PATH
--------------------------------------------------------------------------------
/dev/rdsk/c0d1s0
/dev/rdsk/c0d1s1
/dev/rdsk/c0d1s7
/dev/rdsk/c0d1s9
/dev/rdsk/c1t1d0s0
        /dev/rdsk/c1t1d0s0,即为我们刚才Format的磁盘。c0d1s9这些不是,这些是装系统时建的盘。
        说到这儿,补充一下Solaris下磁盘名的格式:
/dev/[r]dsk/cXtXdXsX
c:逻辑控制器号(逻辑控制器)
t:物理总线目标号
d:磁盘或逻辑单元号(LUN)
s:分区号
如c1t1d0s0表示1号控制器,1号磁盘,0号LUN,0号分区。
        
        分完区后,不需要设置属主,ASM就可以自动识别到了。再把其他的盘也Format一下,让ASM可以识别到。
        我最终选择了这两块盘创建DG:
SQL> create diskgroup dg1 external redundancy disk '/dev/rdsk/c2t1d0s3','/dev/rdsk/c1t3d0s3';
Diskgroup created.
        
        然后我们把Redo挪到ASM的dg1中,我在asmcmd中,在DG1里面建了一个redo目录,然后到数据库实例中,将组2、组3挪进了ASM。在删除组1、再添加组1到ASM中时
,报ASM空间不足。算了,我的磁盘太小了。
alter database drop logfile group 2;
alter database add logfile group 2 ('+DG1/redo/redo2.log') size 60m reuse;
alter database drop logfile group 3;
alter database add logfile group 3 ('+DG1/redo/redo3.log') size 60m reuse;
alter database drop logfile group 1;
        现在我只有两组日志,都在ASM中,对于我们测试来说,两组已经够了:
SQL> select * from v$log;
    GROUP#    THREAD#  SEQUENCE#      BYTES    MEMBERS ARC STATUS           FIRST_CHANGE# FIRST_TIM
---------- ---------- ---------- ---------- ---------- --- ---------------- ------------- ---------
         2          1        489   62914560          1 NO  CURRENT                9400623 07-MAY-12
         3          1          0   62914560          1 YES UNUSED                       0
        好了,开始我们的测试吧。修改一下脚本:
io:::start
{
        printf("%s %s %d %s\n",args[1]->dev_statname,args[2]->fi_pathname,args[0]->b_bcount,args[0]->b_flags&B_READ?"R":"W" );
}
        将上面所加的args[2]->fi_pathname=="/export/home/oracle/opt/dbdata/h1/redo1.log"        这种谓词去掉。
# ./test2.d
dtrace: script. './test2.d' matched 7 probes
CPU     ID                    FUNCTION:NAME
  1   3092             default_physio:start sd5 4096 W
  1   3092             default_physio:start sd2 4096 W
  3   3093              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/control01.ctl 8192 W
  3   3093              bdev_strategy:start cmdk0 /export/home/oracle/opt/dbdata/h1/control01.ctl 8192 W
……………………………………
……………………………………
……………………………………
        由于打开了7个探针,所有IO开始时的操作都会被我们捕获,因此,输出信息会比较多。
        在Oracle 会话中,修改一行数据,再提交,可以观察到这样的IO:
  2   3094                    aphysio:start sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W
        [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url],这一串东西是啥?只要显示一下ASM中的磁盘就明了了,我的ASM DG1 中包含这两块盘/dev/rdsk/c2t1d0s3,
/dev/rdsk/c1t3d0s3。

# ls -lFrt /dev/rdsk/c2t1d0s3
lrwxrwxrwx   1 root     root          64 Mar 17 10:03 /dev/rdsk/c2t1d0s3 -> [url=mailto:../../devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw]../../devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url]*
# ls -lFrt /dev/rdsk/c1t3d0s3
lrwxrwxrwx   1 root     root          50 Mar 17 10:03 /dev/rdsk/c1t3d0s3 -> [url=mailto:../../devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw]../../devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url]
        经过观察,可以发现,刚才的IO,其实是针对/dev/rdsk/c1t3d0s3的,这正是DG1中的一块盘。接下来我们如下修改脚本:
io:::start
/ args[2]->fi_pathname=="[url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url]" ||
  args[2]->fi_pathname=="[url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url]" /
{
        printf("%s %s %d %s\n",args[1]->dev_statname,args[2]->fi_pathname,args[0]->b_bcount,args[0]->b_flags&B_READ?"R":"W" );
}
        我们只观察针对这两个磁盘的IO。执行一条简单的DML,修改一行,我执行如下命令:
SQL> update a1 set id=id+0 where id=2;
1 row updated.
SQL> commit;
Commit complete.
        在提交时,可以发现会有如下的IO:
  3    783                    aphysio:start sd5 [url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url] 1024 W
        每次提交时,都会有IO发生,这是和文件系统的最大区别。这样的话,我们提交的数据,就不会丢失了。有时,也可以观察到512字节的IO。
        再进一步的,我们知道,在ASM中,AU的大小通常是1M,条带大小也为1M。但Redo和控制文件Oracle会将它们设置为“细粒度条带”,条带大小为128K。这个128K
,到底有什么意义呢? 为什么我上面一定要在ASM DG1中,至少搞两块磁盘,就是为了观察这个“细粒度条带”。我们继续。
        
        经过观察,我测试用的两条命令,update a1 set id=id+0 where id=2;,commit,每次执行,都会产生1K的Redo。这种说法也不太准备,更准确应该是这两条命
令,每次执行会触发Lgwr 执行1K的IO写操作。其实这两条语句产生的Redo只有700多字节,但提交的时候,LGWR写Redo数据,只能以块为单位去写。700多字节,正好占两
个日志块,1K字节。
        脚本再做一下修改,完整的脚本如下:
#!/usr/sbin/dtrace -s
BEGIN
{
    i=0;
}
io:::start
/ args[2]->fi_pathname=="[url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url]" ||
  args[2]->fi_pathname=="[url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url]" /
{
        printf("IO number:%d %s %s %d %s\n",i,args[1]->dev_statname,args[2]->fi_pathname,args[0]->b_bcount,args[0]->b_flags&B_READ?"R":"W" );
        i++;
}
        我把之前用过的i++加了进来,主要用来观察IO的次数。好,运行这个程序,然后反反复复的执行上面的DML和提交语句。
# ./test2.d
dtrace: script. './test2.d' matched 7 probes
CPU     ID                    FUNCTION:NAME
  3    783                    aphysio:start IO number:0 sd5 [url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url] 1024 W
  3    783                    aphysio:start IO number:1 sd5 [url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url] 1024 W
…………………………………………省略部分内容…………………………………………
  0    783                    aphysio:start IO number:99 sd5 [url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url] 1024 W
  2    783                    aphysio:start IO number:100 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W
  2    783                    aphysio:start IO number:101 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W
…………………………………………省略部分内容…………………………………………
  0    783                    aphysio:start IO number:227 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W
  0    783                    aphysio:start IO number:228 sd5 [url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url] 1024 W
  0    783                    aphysio:start IO number:229 sd5 [url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url] 1024 W
…………………………………………省略部分内容…………………………………………
  0    783                    aphysio:start IO number:354 sd5 [url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url] 1024 W
  0    783                    aphysio:start IO number:355 sd5 [url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url] 1024 W
  0    783                    aphysio:start IO number:356 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W
  0    783                    aphysio:start IO number:357 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W
^C
        上面的结果,注意IO number:0的值,从IO number:100到IO number:227为止,共128次1K的IO,都是针对,磁盘设
备[url=mailto:“/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw]“/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url]”,从228次IO到355次IO,共128次1K IO,针对另一磁盘设
备[url=mailto:“/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw]“/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url]”。
        这就是细粒度条带的意义。虽然AU大小仍为1M,文件仍是按1M来分配空间。文件的第一个1M,在磁盘1,第二个1M,在磁盘2,等等。但在使用空间的时候,却是按
128K。先在磁盘1写128K,再在磁盘2中写128K,等等。
        最后,还有一个问题,比如第100和101次IO:
  2    783                    aphysio:start IO number:100 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W
  2    783                    aphysio:start IO number:101 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W
        这是两次IO,每次IO的大小是1K,但是,我们如何确定这两次1K大小的IO,一共写了4个日志块?有没有可能100次IO,写了1、2号日志块,101次IO,写了2、3号
日志块,两次1K大小的IO,总共写了1.5K数据?
        之所以有这样的疑问,是因为我的那条DML加提交,产生的Redo量是700多字节。虽然要占两个块,但是占不满两个块。那么,接下来的一次针对Redo的IO,是从没
占满的块继续往下写,还是新占一个块?
        在资料视图中,有一个资料redo wastage,Redo浪费空间。如果两次IO每次各占两个块,redo wastage会不会有增加呢? 我用如下语句查了一下
SQL> col name for a20
SQL> select name,value from v$sesstat a,v$statname b where a.statistic#=b.statistic# and b.name in( 'redo size','redo wastage') and sid=855;
NAME                      VALUE
-------------------- ----------
redo size                774092
redo wastage                  0
        855号会话,就是我反反复复执行那条DML、提交语句的会话。我们没有观察到这个会话中有Redo浪费。通过这个结果,我上面的测试,好像两次相邻的、1K大小的
IO,应该并没有产生共2K的Redo,应该只有1.5K。
        但是,如果是这样的话,我们上面关于“细粒度条带”的结论,就有问题了。因为它是建立在连续128次1K大小的IO,共写了128K Redo基本上的。或者说,是建立
在两次相邻的、1K大小的IO,一定会产生2K的Redo这个基础上。但通过redo wastage分析的结果,好像应该只有1.5K才对。
        到底结果如何,如果要确定的话,我们只能分析每次Redo IO的内容了。请看下节:“获取进程数据”

第四节   获取进程数据


        不久前才看过一个笑话,从哪儿来的我也忘了。说是一名生物学家研究螃蠏的听觉系统,对着螃蠏大吼一声,螃蠏马上跑了。将螃蠏的腿截掉,对着螃蠏再次大吼
一声,螃蠏没有跑。因此证明螃蠏的听觉系统是在腿上。
        我们做测试,千万不要无意中成为这名生物学家。用不严谨的测试、得到错误的结论,然后再用这个结论去分析碰到的问题,可想而知,这会是什么结果。
        好,言归正传,继续上节中的测试,为了得到准确的结论,我们需要获得IO的内容。
        继续做这个分析,需要我们对Redo的块格式,有简单的了解。
        512字节的一个Redo块,前16字节,是块头。块头中第8到11字节,是块所属于日志序列号,第4到7字节,是块编号。接下来要做的,在Start探针中,读出块头,
将每次IO的日志序列号、块号显示出来。
        DTrace中,可以使用copyin,《Solaris 动态跟踪指南》P155页,有这个函数的介绍。P411页,还有更详细的介绍。
        简单点说,某个进程的数据,我们不能直接显示,要使用copyin拷贝到DTrace的缓存中,才能显示。如下修改我们的脚本:
#!/usr/sbin/dtrace -s
char *rd;
char bn[4];
char seq[4];
BEGIN
{
    i=0;
}
io:::start
/ args[2]->fi_pathname=="[url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url]" ||
  args[2]->fi_pathname=="[url=]/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw[/url]" /
{
        rd=copyin((uintptr_t )(args[0]->b_addr),16);
        bn[0]=rd[4];
        bn[1]=rd[5];
        bn[2]=rd[6];
        bn[3]=rd[7];
        seq[0]=rd[8];
        seq[1]=rd[9];
        seq[2]=rd[10];
        seq[3]=rd[11];
        printf("IO number:%d %s %s %d %s Seq:%d,Block:%d\n",i,args[1]->dev_statname,args[2]->fi_pathname,args[0]->b_bcount,args[0]-
>b_flags&B_READ?"R":"W",*((int *)&seq[0]),*((int *)&bn[0]));
        i++;               
}
        解释一下这段脚本,首先,在开头的三个变量定义:
char *rd;
char bn[4];
char seq[4];
        我们在BEGIN探针中,也用过变量:i。在io:::start探针中,还对i做了自增操作。这三个变量与i的区别有两点,一它们是全局变量,二它们有类型。
        只有这样的全局变量,才需要专门在程序开头定义,也只有它们,才可以有类型。可以使用的类型,同C语言一样。变量i我们没有定义,直接就开始使用。DTrace
根据它的值来决定它的类型。
        
        然后,这行是这个脚本程序的关键:
rd=copyin((uintptr_t )(args[0]->b_addr),16);
        (uintptr_t )(args[0]->b_addr),这是我们的地址,我们从这个地址中,考贝16个字节,到rd中。rd是一个字符型指针,它的空间在DTrace缓存中。args[0]-
>b_addr这个域的值,就是存放IO内容的缓存区地址。我们在这个地址前加一个(uintptr_t ),强制类型转换。因为在《Solaris 动态跟踪指南》P155页,我们可以看到:
void *copyin(uintptr_t addr, size_t size)
        copyin要求的地址类型是uintptr_t型。但(args[0]->b_addr)的类型并不是这个,因此,我们需要加个强制类型转换。将(args[0]->b_addr)转成uintptr_t型。
        在这里我拷贝了16个字节,就是整个块头了。下面的赋值语句:
        bn[0]=rd[4];
        bn[1]=rd[5];
        bn[2]=rd[6];
        bn[3]=rd[7];
        seq[0]=rd[8];
        seq[1]=rd[9];
        seq[2]=rd[10];
        seq[3]=rd[11];
        将块头中的4到7字节,传到bn中,这是块号。将8到11字节,传到seq中,这是日志序列号。最后一步输出时,是这样操作bn和seq这两个数组的:
…………,*((int *)&seq[0]),*((int *)&bn[0]);
        &seq[0],取出seq数组中0号元素的地址。(int *),将这个地址转成整型,(int *)&seq[0],是一个整型的地址,指向seq的开头。在它之前再加一个*,取出这个
整型地址中值。其实根本含意就是,将bn数组中的4个字节,当作一个整型变量输出。这是C语言中指针的常见玩法。在使用copyin时,可能会用的比较多。
        好了,运行脚本,执行DML+提交,观察输出结果:
……………………
  3    783                    aphysio:start IO number:93 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W Seq:495,Block:513
  3    783                    aphysio:start IO number:94 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W Seq:495,Block:515
  3    783                    aphysio:start IO number:95 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W Seq:495,Block:517
  3    783                    aphysio:start IO number:96 sd3 [url=]/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw[/url] 1024 W Seq:495,Block:519
……………………
        可以看到第93次IO写的是495号日志第513号块,大小1K。94次IO是495号日志515号块,大小1K,等等。
        也就是说,两次相邻Commit触发的IO,各自写各自的块。两次1K的IO,共写了4个块。
        也就是说,我们对“细粒度条带”的分析是不错的。但redo wastage资料中的值就有问题了,不知道它衡量的是什么浪费。我每次只产生700多字节Redo,用了
1024字节空间,但redo wastage的值为0。

        好了,我们的揭密Oracle之七种武器之二,先到这里吧。第三章将继续介绍DTrace语法,同时结合Oracle的观察结果。
        用一句广告语:精彩继续,敬请期待。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/321157/viewspace-722896/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/321157/viewspace-722896/

你可能感兴趣的:(揭密Oracle之七种武器二:DTrace语法:跟踪物理IO)