SHOUG技术分享——如何诊断和解决high version count

原文链接:

http://www.dbaleet.org/shoug_share_session_how_to_troubleshoot_and_fix_high_version_count_issue/

在Oracle 10g以上的版本,High version count可谓是一个臭名昭著的问题。Hight version count不仅仅产生的原因多种多样,并且会导致各种令人头痛的问题,轻导致数据库的性能急剧下降,CPU利用率剧增,重则导致数据库挂起,触发ORA-04031或者其它bug导致宕机。

什么是verion count,什么是high?

在弄清楚诊断和解决这个问题之前,首先需要清楚什么是version count,什么是high?换而言之就是产生version count的原因,多高的version count才算high。

一个SQL 第一次执行时,会进行硬解析,同时创建parent cursor 和child cursor。

当再次执行这个SQL时,那么首先会对SQL 语句进行特殊的hash 运算,对应生成一个hash value。Hash value存放在parent cursor中,然后会用这个hash value到paranet cursor的bucket中匹配,如果相同的hash value 已存在parent cursor里,则继续遍历这个child cursor,如果可重用,那么就沿用child cursor的信息,如果不能重用,就会重新生成一个新的child cursor。

一个parent cursor下child cursor 的总数,就是这个SQL的version count。

事实上,我们很难去准确定义一个high version count的值,只能根据不同的系统来判断是否为high verison count。在AWR报告中,默认verion count超过20的SQL就会显示在order by version count一栏中。根据经验version count如果超过100,可能就需要引起注意了。

我们可以通过查看v$sqlarea视图的loaded_versions来判断当前这个SQL的version count是多少,然后再通过address的值来查询v$sql_shared_cursor视图看那些字段的返回值为Y,Y代表mismatch。Mismatch是引起产生version count的直接原因。通常我们可以综合:

v$sqlarea, v$sql_shared_cursor, v$sql_bind_metadata, v$sql_bind_captures来诊断这类问题,但是手工去查这些表往往过于繁琐,  Abel Macias开发了一个小工具叫做version_rpt,这个工具可以用来诊断导致是那个模块出现mismatch,从而导致了high version count。我们可以到High SQL Version Counts – Script to determine reason(s) (DOC ID 438755.1)上下载这个小工具。

Cursortrace和cursordump

当然有时候我们会遇到某些SQL的$sql_shared_cursor所有的字段的结果都为N,但是其version count还是很高的情况,那么这些查询这些视图就不管用了,主要的原因是存在部分bug,可能导致v$sql_shared_cursor的信息不准确。例如:

Bug 12539487 – gv$sql_shared_cursor may not show all reasons to not share a cursor (Doc ID 12539487.8)

所以在10g以上版本可以使用cursortrace来查找high version count的原因。

可以使用以下方式打开cursortrace:

SQL>alter system set events

'immediate trace name cursortrace level 577, address <hash_value>';

其中可以使用三个level,level 1为577, level 2为578, level 3为580(实际上还有其它level,只是没有文档记载)

如需关闭cursortrace,则可以使用以下方式进行关闭:

SQL>alter system set events

'immediate trace name cursortrace level 2147483648, address 1';

或者使用以下方式关闭:

SQL>alter session set events 'immediate trace name cursortrace level 128 , address <address>';

当然也可以通过oradebug的方式来进行cursortrace,这里就不详述了。

注意在10.2.0.4以下版本存在Bug 5555371导致cursortrace无法彻底关闭的情况,最终导致其trace文件不停的增长,从而可能导致oracle文件系统被撑爆的现象。

在实际帮助客户处理查找high version count的原因时,我们发现即使在10.2.0.5以上的版本也可能会出现cursortrace无法彻底关闭的现象。

如果数据库版本在10.2.0.4以下,生产系统上不建议使用cursortrace

生产环境在10.2.0.4以上版本,建议谨慎使用cursortrace。例如注意观察,如果无法关闭则通过修改MAX_DUMP_FILE_SIZE参数限制cursortrace trace文件的最大大小,或者写一个crontab的job定期清理其trace文件。

在11g中引入cursordump,我们可以使用如下方式进行cursor dump:

alter system set events 'immediate trace name cursordump level 16'

这种方式收集的信息比较全:例如它可以采集到部分别的方式无法看到的px_mismatch以及它会进一步展开optimizer_mismatch的信息等。

在10gR2版本中, 我们更倾向于使用processstate dump和errorstack的信息来替代cursordump,因为processstate dump中也存在cursordump的信息:

先找到high version count SQL对应的spid,然后执行以下SQL:

SQL>oradebug setospid

SQL>oradebug ulimit

SQL>oradebug dump processstate 10

SQL>oradebug dump errorstack 3

 

一些可能导致问题的SQL和配置

我们通常可以使用AWR和ASH来找出这些可能存在问题的SQL。根据以往的经验,最容易导致出现version count的SQL包括以下类型:(只是列出最常见的写法,并不是很全)

1.       Insert语句使用绑定变量

Insert into table(column1, column2, column128, …) values (:1,  :2, :3, … :128, …)

尤其是对于某些表字段类型特别多的,并且把这个表所有字段都写到insert语句里面的表现得尤为明显。

2.       select into 语句使用绑定变量

select a, b, c, … into :1,:2,:3 from table1

3.       使用insert … returning语句

4.       使用一个很长的inlist,并且里面都是绑定变量

select * from table where column1 in (1,  :2, :3, … :128, …)

5.       SQL非常长并且里面带有多个绑定变量

如果一个SQL语句特别长,并且使用了绑定变量,那么这样的SQL更倾向于出现high version count。

6.       Dblink调用SQL语句,最好不要使用绑定变量

Bug 12320556  High version count for child cursors referencing a remote object due to AUTH_CHECK_MISMATCH

这一类语句其实从应用的层面都比较好改写,改写的思路如下:

  1. 对于insert into 或者select into可以考虑不要使用绑定变量。
  2. 对于一个字段很多的表,在insert的时候不要把所有字段都列出来
  3. 尽量不要使用insert into这样生僻的写法
  4. 对于inlist 应该控制其内部绑定变量的个数,如果无法控制,则可以将这些变量存入到一张临时表中,去掉inlist,要用的时候再从临时表获取。
  5. 尽量不要写特别长的SQL语句,这种SQL不仅易读性差难于维护,并且很容易导致shared pool的一些争用。
  6. 尽量避免在dblink调用的SQL语句中使用绑定变量。

在配置方面, cursor_sharing和Adaptive cursor sharing的设置不当容易导致high version count的问题。

请不要使用cursor_sharing=similar ,这句话再怎么强调都不过分。

ANNOUNCEMENT: Deprecating the cursor_sharing = ‘SIMILAR’ setting (Doc ID 1169017.1)  

在10gR2以上版本,cursor_sharing设置为similar可能会导致各种各样的bug,其中之一就是可能会导致出现大量的不可共享的child cursor,从而引发出high version count的问题。

Oracle在12c已经不支持cursor_sharing=similar, 在11.2.0.3版本,cursor_sharing设置为similar与设置为force的效果相同。

另外cursor_sharing最好设置为exact,在没有经过充分的测试下,不要将其设置为force。因为设置为force同样有一定的几率可能导致high version count。

参见: High Version Count with CURSOR_SHARING = SIMILAR or FORCE (Doc ID 261020.1)

如果SQL语句的共享性很差,首先要做的应该是对SQL进行调整,而不是调整cursor_sharing参数。

在11g中引入的adaptive cursor sharing(ACS)特性。在没有经过充分测试之前,请关闭此特性,这个特性很容易导致high version count的问题。

Bug 12334286  High version counts with CURSOR_SHARING=FORCE (BIND_MISMATCH and INCOMP_LTRL_MISMATCH)

Bug 7213010 – Adaptive cursor sharing generates lots of child cursors

Bug 8491399 – Adaptive Cursor Sharing does not match the correct cursor version for queries using CHAR datatype

常见的bug和workaround

在10gR2版本中存在一个臭名昭著并且隐匿得很深的Bug,它们曾经折磨过无数DBA:

Patch 6795880: BATCH JOBS HANG WAITING ON ‘KKSFBC CHILD COMPLETION’

Bug 8575528  Missing entries in V$MUTEX_SLEEP.location

这两个bug本质是同一个问题,只是因为前一个问题修复不彻底,反而导致了后一个问题。它存在的缺陷在于如果high version count的SQL,那么去查找child cursor的过程中效率会非常低,从而导致kksSearchChildList/ kqlfMutexClnFetch这些过程会导致挂起。数据库的等待事件上表现为大量的latch,mutex等待,典型的有latch: library cache lock, kksfbc child completion, cursor: mutex pin S, Latch: Row Cache Objects, library cache: mutex X。而这些等待事件基本都是平时难得一见的奇观。

最终的结果往往有两种:

  1. 数据库一直挂起必须手工重启;
  2. 数据库挂起一段时间,然后恢复
  3. 系统资源耗竭导致宕机
  4. 触发出其它的bug例如某ORA-600 [kkssearchchildlist*]或者ORA-07445[kkssearchchildlist*]导致数据库宕机

这个问题号称在10.2.0.5已经修复,但是这个问题在10.2.0.5版本上依然很常见。主要原因有两点:

  1. 10.2.0.5虽然包含了修复这个问题的代码,但是默认情况下是不生效的,需要用户手工将”_cursor_features_enabled” = 10。
  2. 即使设置了”_cursor_features_enabled” = 10,依然还有遇到KKSFBC CHILD COMPLETION的概率,根本原因还是在于child cursor过多,这一系列的函数调用过程依然过于低效。

遗憾的是如果在10.2.0.5以上版本碰到这个问题,除了按照上文的要求调整SQL或者升级到11gR2以外,从数据库现有的手段几乎无解。

另外一个常见的问题是数据表义使用varchar类的变长字符串类型,而应用可能传入的字符串长度为个位数,也可能传入的字符串长度为四位数。换而言之,就是应用程序传入变量的长度范围过大,导致bind mismatch,最终child cursor不能共享从而重新进行hard parse,这样的结果就导致child cursor的急剧膨胀。

根据

This is due to the bind buffer mismatch of the current child cursor. If oracle is unable to bind the current value to the existing child cursors bind buffer, oracle upgrades the existing child cursor with a high bind buffer. This will force the query to do a hard parse and a new child cursor will be created. The previous one will be marked ‘dont use’. 

这个问题可以通过设置固定的字符串buffer的长度来减少其对应的version count。通过level为4000的10503事件来达到此目的,注意这里的4000为字符串buffer的长度。在SQL中可变字符串varchar2最大的长度为4000。

SQL>alter system set events '10503 trace name context forever, level 4000';

还有一个非常有杀伤力的Bug为

Bug 8981059  High Version Count (due to USER_BIND_PEEK_MISMATCH) with bind peeking

这个Bug几乎影响了所有的10gR2的版本。

这个bug典型的cursor dump信息如下:

KKSCS sharing succeed xsc=1106ac818 childno=3370 reason=BND

Checking for already pinned child. fbcflg 1

Object is invalid

No valid child pinned

Parent 70000065e35bb50(70000065f702598) ready for search

kksSearchChildList outside while loop

kksCheckCursor: next child is #3370

kksCheckCursor: pinning child #3370 in shared mode 70000065e35b960

0000065f41e8f0

KSCS sharing succeed xsc=1106ac818 childno=3370 reason=NFP

KSCS sharing succeed xsc=1106ac818 childno=3370 reason=SQT

KSCS sharing succeed xsc=1106ac818 childno=3370 reason=OPT

...

KSCS sharing succeed xsc=1106ac818 childno=3370 reason=BDM

...

KSCS sharing failed xsc=1106ac818 childno=3370 reason=UBP

kksUnlockChild: releasing child

Failed sharing : 8000000

kksCheckCursor: next child is #3402

kksCheckCursor: pinning child #3402 in shared mode 70000065e35b960

0000065e615f88

KKSCS sharing succeed xsc=1106ac818 childno=3402 reason=NFP

KKSCS sharing succeed xsc=1106ac818 childno=3402 reason=SQT

 

在堆栈信息中还会找到kksfbc以及 kksLoadChild这样的函数。

这个问题实际上是因为绑定变量窥测导致的child cursor不能共享。并且在某些情况下可能导致查询出来的结果是错误的,也就是我们所说的wrong result。

通常这一类问题在10.2.0.5以上版本可以通过关闭绑定变量窥测来规避:

SQL>alter system set "_optim_peek_user_binds"=false
  • 监控version count特别高的语句,如果到了特定的阈值,就将其从shared pool中踢出去,在10.2.0.4和10.2.0.5通过以下方式清除特定的SQL:
SQL>alter session set events '5614566 trace name context forever';

SQL>exec dbms_shared_pool.purge('&address, &hash_value', c);

前面event 5614566设置的目的是为了规避Bug 5614566导致的使用dbms_shared_pool.purge 无法将parent cursor清除出shared pool的问题。

虽然Bug 5614566在10.2.0.5已经修复,但是在某些情况下,依然会遇到dbms_shared_pool.purge的情况。

  • 使用dbms_shared_pool.keep这个包将特定high version count的SQL进行keep。

此建议为Bug 10157392  High version counts for SQL with binds (BIND_MISMATCH)给出的一个workaround,这种方法大多数情况都不起作用。

  • 定期的对shared pool进行flush。

在某些负载比较低的系统中,可以考虑使用这种方法来防治high version count的问题。在业务繁忙的系统中刷新shared pool存在很大的风险,以来大量被刷出去的SQL需要重新进行硬解析,有可能会导致CPU短时间内的迅速增加。还有就是某些SQL被刷出shared pool以后重新解析其执行计划可能会发生变化,由此容易造成性能的不稳定。

11g的cursor特性的增强

在10gR2中,child cursor的最大上限为32768。Child cursor的数量如果超过了32768,那么这个session就会抛出ORA-600 [17059]的错误,然后这个session就会crash,在10.2.0.4和10.2.0.5上有一个增强补丁:Bug 8946311  Increase max children before reporting ORA-600 [17059]。如果应用了这个patch,那么child cursor的上限就可以提高到65535了。

但是允许child cursor的上限到一个非常大的值并不是什么好主意,只能暂时缓解因为high version count而导致的ORA-00600的错误。但是同时也会引发新的问题,例如最典型的是因为child cursor过多,导致了单个SQL语句占用的shared pool的空间非常大,最终导致ORA-004031.

到了11g,Oracle已经充分意识到high version count是一个很严重的问题,尽管mismatch是客观存在的,但是可以通过其它的一些手段限制child cursor的数量。

Enhancement Request Bug 10187168 : OBSOLETE PARENT CURSORS IF VERSION COUNT EXCEEDS A THRESHOLD   引入了一个隐含参数叫_cursor_obsolete_threshold,此参数用来限制单个parent cursor下child cursor的数量,默认值为100。如果child cursor的数量超过了这个阈值就会cursor obsolescence的特性,也就是说这个时候parent cursor会被废弃,并且同时重新创建一个新的parent cursor。 这样虽然mismatch会继续存在,但是一劳永逸的解决的high version count的问题。这个patch已经集成到11.2.0.3版本。如果低于11.2.0.3版本,除了需要应用这个patch,同时需要设置一下参数:

11.2.0.1:

SQL> alter system set "_cursor_features_enabled"=34 scope=spfile;

SQL> alter system set event='106001 trace name context forever,level 1024' scope=spfile;

11.2.0.2:

SQL> alter system set "_cursor_features_enabled"=1026 scope=spfile;

SQL> alter system set event='106001 trace name context forever,level 1024' scope=spfile;

 

小结

High version count由于产生原因纷繁多样,并且产生的结果表现形式各不相同,再加上其复杂的诊断方法,往往令很多DBA都束手无策,这不能不说是早期产品设计上的一个缺陷。好在从11gR2开始,通过_cursor_obsolete_threshold这个特性,我们很快可以和high version count说再见了。但是探索的过程永远比结果重要,我们收获到的不仅仅是知识,而是一种钻研的精神。

Comment

*

沪ICP备14014813号

沪公网安备 31010802001379号