当前位置: 首页 > news >正文

调试Oracle 之一 基础篇

Author:  Lv, Haibo.

前言

 “多年之后,面对枪决行刑队,奥雷良诺•布恩迪亚上校将会想起,他父亲带他去见识冰块的那个遥远的下午”。

《百年孤独》的这个开头堪称经典。倒序的方法,加上时间状语“多年之后”,苍桑感一下子就出来了。何况又是面对行刑队,悲剧感也很强烈。要知道文学作品最能打动人心的,就是悲剧。比如,我至今还记得最短、最悲伤的爱情小说:

“我爱你!!!”

“滚。”

全篇只有四个字,但悲剧色彩强烈。一下子让我想到了我哪坎坷的青少年时期。

好了,不扯了,大家没有看错,这是一篇技术文章。我们言归正传。

“多年之后,麻木的面对一个个一筹莫展的Oracle难解之谜时,我总会想起来,当年我是如何积极的想成为一名DBA”。

 

第一节  调试Oracle:DBA的新领域

职业的发展,总会经历一段特殊的时间段,有叫“舒适区”,有叫“天花板”。总之,就是你感觉前进困难、进步无门,而待在原地则很舒适,那么恭喜你,你就在处在这段区域中了。

我是2004年开始转DBA,2005年正式成为DBA。5年后,2010年,我进入了我的“舒适”区。我感觉处理Oracle故障也就哪么几种方式,搞不定的问题丢给Oracle售后。我也想着进一步去钻研Oracle的原理、进一步提升自己的技能。但毕竟看不到源码,只能根据有限的几条Dump命令去探索,如同隔靴搔痒、雾里看花一般,这样研究出来的“底层原理”,准确性都不好确定,更别谈用这样的原理去排故、调优。

好像有一道无形的天花板,挡住了我的前进之路一般。既然前进无门,原地踏步又很舒适,还有谁会想着前进呢?

这时回想当年自己彻夜钻研Oracle技术,好似隔世一般。

当年我热火朝天的从开发转到DBA时,一定没有料到最终会是这样的结果。我就像奥雷良诺•布恩迪亚上校,被枪决在技术的天花板前。或者,也可以换种好听点的说法,成为在舒适区的温水中,被煮死的青蛙。

微博上有人辟谣,温水煮不死青蛙。我也是这样认为的,青蛙又不傻,当水温升高到一定程度时,难道它不会跳出来?

无论温水煮不煮的死青蛙,但温水一定煮的死人。我不想被温水煮死。

还有人为鸵鸟辟谣,传说当危险来临时,鸵鸟会把头埋到沙子中。由此还产生了一个新词:鸵鸟心态。我也一直认为,鸵鸟如果真的哪么傻,它是如何在这个充满竞争的星球存活亿万年的。

同样,鸵鸟有没有鸵鸟心态不太清楚,人多多少少都会有的。

 

我想打破我的天花板,我不想用鸵鸟心态去面对问题,我更不想待在“温水舒适区”等死。如何跳离“温水”呢?

答案就是:“调试Oracle”。

调试Oracle真的有这么神奇吗。是的。它可以有助于我们打破天花板,帮助我们跳离“温水”。当然前提是你要先有跳出温水的意愿。

如果非要让我用一句话总结“调试Oracle”的最大用途,我觉得,就是这7个动人的字:“明明白白我的心。”

 

维护Oracle这种大型软件,仅仅会运用它提供的命令是远远不够的,必须要了解它底层的原理。

比如,我知道体温超过38度可以吃两片白加黑,如果咳嗽的话可以来一瓶川贝枇杷膏,……,根据常识,我知道身体常见的症状要吃什么药,我能当医生吗?我当了医生你敢让我看病吗?

多多少少,我们要了解一下身体的运行原理,才能当医生。

比如体温升高我们可以去验个血,看一下白细胞比例,如果偏高,说明有病菌,属于病毒性感冒。原理是白细胞是杀死病菌的,如果体内病菌增多,人体就会产生更多的白细胞,杀死病菌。所以观察到白细胞增多,就意味着身体检测到病菌入侵了。

这种情况下如果体质好的话,就不必吃药,等着体内的白细胞自己杀掉所有的病菌。如果体质不好,可以吃一些抗菌药,等等。

我不是医生,也没学过医,上面这些都是道听途说,不知道对不对。只是拿来做个比喻。

Oracle的运维,道理差不多的。会使用Oracle提供的命令只是学习Oracle的第一步,必须要懂得原理,才能慢慢提高自己。

但Oracle毕竟是闭源软件,又看不到源代码,要如何去分析、挖掘它的原理呢?“调试Oracle”就是用来打开这个黑盒子的,掌握了它,我们就可以随意挖掘你感兴趣的原理。

 

本来我想用一两个例子,快速展示一下调试Oracle的神奇之处。但发现这可能比较困难,因为我必须从头说起,如果没有一点基础,你可能很难明白我们接下来要说的案例。下面,我们先来了解一下DTrace的基础。

 

DTrace是一门简单的语言,了解任何语言,都要先从Hello World开始,要不然,就会显的你很不专业。我当然不能坏了规矩。

 

第二节 DTrace基础: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。当然,我们调试进程,一般不需要修改,只需要观察。(偶而也需要修改,比如,有时我们要修改DBWR、LGWR等Oracle核心进程的触发时间,从3秒改成我们需要的时间。)

这就是DTrace的原理。我们将上文中的术语换成DTrace中的术语,触发器就是Probe,可以译为探针。探针并时都是关闭的,也就是Disable的。我们可以使用DTrace,打开探针,并为探针指定动作。当探针被触发,你通过DTrace为探针定义的动作,就会被执行。

 

好了,让我们从Hello World开始吧。下面是DTrace版的“Hello World”:

在root下,vi test1.d,输入如下命令:

BEGIN

{

    printf("hello world,www.ebay.com ");

    exit(0);

}

 

如下执行此脚本:

# dtrace -s test1.d

结果显示:

dtrace: script 'test1.d' matched 1 probe

CPU     ID                    FUNCTION:NAME

  3      1                       :BEGIN hello world,www.ebay.com

 

上面就是显示结果了。下面对于脚本程序和输出结果,略加说明:

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.ebay.com,就是我们程序的输出结果了。

 

最后,每次运行此程序时,都要dtrace -s,太麻繁了。我们可以添加一个#!/usr/sbin/dtrace -s在程序头,如下所示:

 

#!/usr/sbin/dtrace -s

 

BEGIN

{

    printf("hello world,www.ebay.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.ebay.com

 

第三节  详述探针(Probe)

完整的探针描述符,绝对不止上节我们遇到的BEGIN这么简单。它包括PROVIDER(提供器),MODULE(模块名),FUNCTION(函数名)和NAME(探针名称)四部分。BEGIN只是最简单的一个特例。

PROVIDER是最上层的名称,比如有IO PROVIDER,进程PROVIDER,等等。每种PROVIDER根据其包含的探针不同,又分为N种MODULE。MODULE之中又包含各种FUNCTION,最后的NAME是探针名,通常是进入、开始、退出、完成这些东西,在进入一个FUNCTION(函数)、退出函数、完成函数等等动作发生时被触发。

简单点说,PROVIDER、MODULE、FUNCTION、NAME就像姓、名、字、号,姓李,名白字太白,号诗仙。PROVIDER为李,MODULE为白,FUNCTION为太白,NAME为诗仙。

也就是说,它们都是“名字”的不同部分,不必理解的太为复杂。

我们以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探针。或者,直接称它为io:genunix:bdev_strategy:start探针。

 

一个探针的称呼其实无所谓。了解Solaris一供为我们提供了什么PROVIDER(提供器),这些提供器下都有什么MODULE(模块),这些模块中都有什么FUNCTION(函数),以及这些函数上都有什么探针,这才是重要的。关于这点,我们可以参考《Solaris 动态跟踪指南》,这是本书像一本字典,详细介绍了所有的提供器、模块等等。其实它就是DTrace的官方文档。难得Oracle还有翻译的如此好的文档。

 

好,先以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,这是Oracle最小的I/O了。

我们为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个探针。

好了,基础就补到这。其实从这里的简单例子,我们已经可以分析挖掘很多的东西了。但这些东西找个其他的IO跟踪工具也可以跟踪出来。下面,正式内容开始,我们开始调试Oracle。这是别的工具所完成不了的。

 

精彩内容,请期待“调试Oracle之二:实例篇。”

 

* 本文版权和/或知识产权归eBay Inc所有。如需引述,请和联系我们DL-eBay-CCOE-Tech@ebay.com。本文旨在进行学术探讨交流,如您认为某些信息侵犯您的合法权益,请联系我们DL-eBay-CCOE-Tech@ebay.com,并在通知中列明国家法律法规要求的必要信息,我们在收到您的通知后将根据国家法律法规尽快采取措施。

相关文章:

  • 基于Apache Mesos 构建高可靠,高可用的Jenkins CI
  • Kepler性能分析之M2E调优
  • Ebay开源 Pulsar:实时大数据分析平台
  • JS组件化验证检测
  • 基于云技术的集成测试代码覆盖率收集的一站式解决方案
  • 使用github pages + issues + api建立个人博客
  • MapReduce的详细过程
  • 基于Jmeter和Jenkins的自动化性能测试的一站式解决方案
  • jQuery动态载入JS文件研究
  • SolrCloud之分布式索引及与Zookeeper的集成
  • Kafka的分布式架构设计与High Availability机制
  • JS方法代理
  • Hadoop作业性能指标及参数调优实例 (一)Hadoop作业性能异常指标
  • Hadoop作业性能指标及参数调优实例 (二)Hadoop作业性能调优7个建议
  • Hadoop作业性能指标及参数调优实例 (三)Hadoop作业性能参数调优方法
  • 【RocksDB】TransactionDB源码分析
  • 【前端学习】-粗谈选择器
  • Apache Pulsar 2.1 重磅发布
  • canvas 五子棋游戏
  • Just for fun——迅速写完快速排序
  • mysql_config not found
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • vue.js框架原理浅析
  • Webpack入门之遇到的那些坑,系列示例Demo
  • XML已死 ?
  • 初识MongoDB分片
  • 搞机器学习要哪些技能
  • 聚簇索引和非聚簇索引
  • 你真的知道 == 和 equals 的区别吗?
  • 如何设计一个微型分布式架构?
  • 携程小程序初体验
  • # Apache SeaTunnel 究竟是什么?
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • (LeetCode C++)盛最多水的容器
  • (Redis使用系列) SpirngBoot中关于Redis的值的各种方式的存储与取出 三
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • **CI中自动类加载的用法总结
  • .NET 2.0中新增的一些TryGet,TryParse等方法
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NET/C# 的字符串暂存池
  • .netcore 获取appsettings
  • .NetCore项目nginx发布
  • .NET基础篇——反射的奥妙
  • .net开发时的诡异问题,button的onclick事件无效
  • /proc/vmstat 详解
  • @Autowired和@Resource的区别
  • @Bean, @Component, @Configuration简析
  • @Service注解让spring找到你的Service bean
  • []使用 Tortoise SVN 创建 Externals 外部引用目录
  • [100天算法】-不同路径 III(day 73)
  • [2019/05/17]解决springboot测试List接口时JSON传参异常