阿里云
发表主题 回复主题
  • 575阅读
  • 9回复

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

级别: 小白
发帖
0
云币
-41



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


(第一章地址:

揭密Oracle之 七种武器  第一章 搭建测试环境

http://www.itpub.net/thread-1605241-1-1.html

或参考我的BLOG www.MythData.com


揭密Oracle之七种武器之三:破译古老的谜题---共享CBC Latch的秘密

http://www.itpub.net/thread-1617245-1-1.html





        其实本章是以物理IO为例,介绍DTrace的基本语法。本篇内容还是比较多的,应该足够有兴趣的朋友学习一周了。下周,再推出我的第三章。揭密Oracle之七种武器二:DTrace语法:跟踪物理IO-

         另外说明一下,我并不是DTrace专家,我用DTrace主要目的是研究Oracle。我们不会对DTrace作过多深入的介绍(不过DTrace本身也没有深入的东西,调试工具吗,复杂的是要调试的目标),我们的主要内容,还是逐步深入,介绍如何用DTrace分析、研究Oracle方法。

        授人以鱼,不如授人以渔。我相信,方法比结果重要。


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

这个是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 &quot揭密Oracle之七种武器二:DTrace语法:跟踪物理IO-;

    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 &quot揭密Oracle之七种武器二:DTrace语法:跟踪物理IO-;

    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 <none>

  1    781              bdev_strategy:start <none>

  1    781              bdev_strategy:start <none>

  1    781              bdev_strategy:start <none>

  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    <----------------------------------format ,格式化命令。

Searching for disks...done

AVAILABLE DISK SELECTIONS:

       0. c0d1 <DEFAULT cyl 2085 alt 2 hd 255 sec 63>

          [url=]/pci@0,0/pci-ide@7,1/ide@1/cmdk@1,0[/url]

       1. c1t1d0 <DEFAULT cyl 98 alt 2 hd 64 sec 32>

          [url=]/pci@0,0/pci15ad,1976@10/sd@1,0[/url]

       2. c1t3d0 <DEFAULT cyl 98 alt 2 hd 64 sec 32>

          [url=]/pci@0,0/pci15ad,1976@10/sd@3,0[/url]

       3. c2t0d0 <DEFAULT cyl 98 alt 2 hd 64 sec 32>

          [url=]/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@0,0[/url]

       4. c2t1d0 <DEFAULT cyl 98 alt 2 hd 64 sec 32>

          [url=]/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0[/url]

Specify disk (enter its number):1    <-------------------------除了c0d1是我安装系统时,原始创建的磁盘外,剩下四个是后面添加的。此处我选择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

        !<cmd>     - execute <cmd>, then return

        quit

format> fdisk    <------------------------应该先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 <-----------------------------此处输入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

        !<cmd> - execute <cmd>, 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  <-------2号分区已经被系统使用

  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  <-------8号分区也已经被系统使用

  9 unassigned    wm       0             0         (0/0/0)       0

partition> 0   <--------------------------------------选择第0号分区,也可以选择1号、3号或其他的,但不能选择2号、8号这两个已经占用的。

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            <-------- 如果要用ASM,此处要从1开始,否则可以从0开始

Enter partition size[0b, 0c, 0e, 0.00mb, 0.00gb]: 95mb  <-------- 输入大小

partition> label   <-------------设置label

Ready to label disk, continue? y  <------------输入y

partition> quit    <-------------退出partition,回到Format

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

        !<cmd>     - execute <cmd>, then return

        quit

format> type          <----------------------------选择类型

AVAILABLE DRIVE TYPES:

        0. Auto configure

        1. DEFAULT

        2. other

Specify disk type (enter its number)[1]: 1    <---------------------此处选DEFAULT

selecting c1t1d0

[disk formatted]

format> save                   <-----------------------------此处,输入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 <none> 4096 W

  1   3092             default_physio:start sd2 <none> 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 -> ../../devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw*

# ls -lFrt /dev/rdsk/c1t3d0s3

lrwxrwxrwx   1 root     root          50 Mar 17 10:03 /dev/rdsk/c1t3d0s3 -> ../../devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw

        经过观察,可以发现,刚才的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,都是针对,磁盘设

备“/devices/pci@0,0/pci15ad,1976@10/sd@3,0:d,raw”,从228次IO到355次IO,共128次1K IO,针对另一磁盘设

备“/devices/pci@0,0/pci15ad,790@11/pci15ad,1976@3/sd@1,0:d,raw”。

        这就是细粒度条带的意义。虽然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的观察结果。

        用一句广告语:精彩继续,敬请期待。



级别: *
发帖
*
云币
*


楼主辛苦,赞一个
级别: *
发帖
*
云币
*


好文!LZ加油!
级别: *
发帖
*
云币
*




这是第二篇,前面还有个第一篇。

慢慢跟着来,就不陌生了。揭密Oracle之七种武器二:DTrace语法:跟踪物理IO-
级别: *
发帖
*
云币
*


itpub的水很深啊
级别: *
发帖
*
云币
*




……

慢慢慢慢的~我们就老了~
级别: *
发帖
*
云币
*


揭密Oracle之七种武器二:DTrace语法:跟踪物理IO-
级别: *
发帖
*
云币
*


牛逼,深入kernel啊
级别: *
发帖
*
云币
*


老吕你这个对一些人来讲是完全陌生的领域, 我沙发
级别: *
发帖
*
云币
*


不错,赞一个
发表主题 回复主题
« 返回列表
«12345678910»
共10页
上一主题下一主题

限100 字节
批量上传需要先选择文件,再选择上传
 
验证问题: 79 + 17 = ?
上一个 下一个

      版权声明

      开发者论坛为你提供“揭密Oracle之七种武器二:DTrace语法:跟踪物理IO”的内容,论坛中还有更多关于 zigbeecc2530pci1230io卡ios脚本文件探针 的内容供你使用,该内容是网友上传,与开发者论坛无关,如果需要删除请联系zixun-group@service.aliyun.com,工作人员会在5个工作日内回复您。