作者:林印豪,新炬网络高级技术专家。
“57.8这套系统CPU又100%啦,新炬同事赶紧帮忙看一下。”远处传来了客户的声音。
我不慌不忙的打开终端,轻叹口气道:“唉,又是这套库,自从我来这边,已经查了2遍了,而且其他人也查过好几遍了,问题很难定位啊!“
前面我查了几次都觉得是SQL硬解析的问题,可是把这个事情反馈过去之后。他们给我们发了以前的statspack报告,发现硬解析过去是300次/秒,现在下降到了60次/秒,Latch free却增加了不少,着实摸不着头脑。”通过前期的排查基本上定位出了Latch Free的争用是由于library cache引起的。当系统出现大量的Library cache争用的时候,CPU就会达到100%。但是原因?最重要的原因一直未找到。我认为是硬解析导致的,也有同事认为是shared pool太小导致的。还有同事认为是几条逻辑读较高的语句导致的。众说纷纭,也没有一个准。基本上这些方法客户都尝试过了,仍然未能解决这个问题。
前期的排查让我们确信的一点是CPU的消耗是在Library cache争用上面,那么Library cache里面情况又是如何的呢?为了一探究竟,我决定在出问题期间直接查询v$libarycache视图,找寻根源。
从v$librarycache视图中我们发现了一个问题,在对SQL AREA这种对象在pin的时候发生了丢失,可以看到pin hint这项命中率只有区区的25%。而在其他上面都接近100%。那我们来具体的看一下library cache的结构。
在我们寻找hash bucket,定位cursor的handle,还有寻找具体的child cursor,或者是其依赖的对象,都需要将其pin住。然后做完操作在unpin。
发现pin的问题后,我根据文档的提示检查了v$latch_misses视图,通过这个视图我们可以看到在函数kglpnc:child和kglupc:child上面sleep_count和wtr_slp_count两个值是很高的。sleep_count字段记录进程在此位置hold对应latch而处于sleep状态的次数,而wtr_slp_count记录此内核位置request对应latch的次数。通过文档我们发现kglpnc:child和kglupc:child正好对应了pin和unpin的函数操作。
在网上和Mos上胡乱搜索一通,还真找到了一个解决办法,可以将参数” cursor_space_for_time”改为true,能有效的将部分cursor给pin在内存中,这样就可以减少pin和unpin产生的次数,不过修改这个参数又是有风险的。长期把这些cursor pin在shared pool内存当中,内存一定要足够大才行,不然容易报ORA-04031的错误,毕竟这可是9i的数据库呀。另外一个风险就是你的应用有cursor泄漏,那么泄漏的cursor会浪费大量内存并在一段时间的运行之后对性能产生负面影响。
所以当我们提出修改这个参数的时候,客户也说了:”年关将至,我们最好保守一些,等过完年之后在大动干戈,现在是要想办法让他把这段时间撑过去。“
既然要求我们保守一点,那就需要寻找问题的根源。这就需要找到引起Library cache争用的具体的对象。通过对系统的监控,我们发现了下列可疑的信息。
可以从图中看到。我们的Library cache争用主要集中在table_这一类的东西上面,那”table_”代表着什么呢?
通过一系列的文档和资料发现叫做伪游标(pseudo cursor)。首先我们在执行一条SQL语句的时候会在内存里面申请一块区域存放我们的游标,这个内存区域就是我们的Library cache。我们可以通过v$open_cursor查询我们打开的游标,同时我们也可以通过我们的v$sql、v$sqlarea查询执行过的游标,但是会有一个奇怪的现象,一部分v$open_cursor中打开的游标在v$sql或者v$sqlarea中是查不到的。那么这一类不存在的游标有一些就是我们的伪游标了。伪游标的具体表现形式就类似”table_4_9_d6c6_0_0_0“。
伪游标是怎么产生的呢?在Oracle访问数据字典表、Lob字段或者Nchar、Nvarchar2的时候,Oracle内部都会提供伪游标这个结构去直接访问数据库对象,这么做的好处是省去了写一个显示的Select语句的开销。
还有一些时候,我们的AWR报告会显示下面的一系列的内容。显示**SQL Text Not Avaliable **,这类的SQL有SQL ID,可是没有SQL Text,这类的SQL也可能是伪游标。
前面介绍了伪游标是怎么产生的,那么如何查询伪游标是在那个对象产生的呢?我们可以通过v$open_cursor查询到打开的伪游标,可以看到它的展现形式是“table_4_9_d6c6_0_0_0”,那么这个中间有一个d6c6,我们需要将这个十六进制的字符串转换成十进制,转换成十进制后就是54982,这个值对应我们的object_id。然后我们就能把object_name找出来。
还有一个办法就是在文档1298471.1提供了一个脚本可以直接帮我们查到相关的对象。脚本内容如下:
找到了这些伪游标,我们又找到了其相关的对象,接下来就是找到和这些对象相关的SQL语句,最保守的做法就是把能想办法把一部分LOB类的SQL进行改造。或者是高峰期错开运行。
上一篇:对一次drop分区引发性能问题的思考
下一篇:软件全生命周期管理体系建设