游标共享Cursor是Oracle学习过程中的一个重点和难点。Oracle的Cursor是建立在对SQL解析Parse消耗的优化上的。根据不同的内存缓存结构,我们经常可以听到诸如:硬解析Hard Parse、软解析Soft Parse和软软解析的名词概念。
严格的说,游标共享的概念基础是游标。Oracle中,游标可以分为Shared Cursor和Private Cursor两个大的类型。理解游标的前提,一定要区分出这两个游标类型。
1、Shared and Private Cursor
Shared Cursor大家谈的比较多,就是驻留在Library Cache里面的缓存对象,其中保存着之前解析好的执行计划。当一个SQL语句第一次出现在系统中,Oracle在Library Cache中没有找到对应的“现成”执行计划,就会启动硬解析Hard parse过程,在Library Cache中生成一个Shared Cursor。注意:这个SQL Cursor可以被其他“符合游标共享条件”的其他会话session共享。在没有被age out或者flush出内存前,都是可以共享。如果第二次发出相同SQL语句,共享了Shared Cursor,我们称之为Soft Parse。
而与Shared Cursor对应的就是Private Cursor。Private Cursor是驻留在Server Process的PGA空间里的。当我们发出SQL或者手工创建一个Cursor,都会在Server Process对应的PGA空间里创建出一个Private Cursor对象。
顾名思义,Private Cursor的含义是只能被当前Session使用,不能实现session间共享。但是,相同一个Session,如果多次执行,是不是需要多次的创建Private Cursor呢?这个过程涉及到的问题就是Private Cursor的共享问题。
我们在一些资料里面可以看到一些混淆概念。说一个SQL只有执行三次之上,才能进行共享。如果我们进行简单的实验,就可以发现这个论断在shared cursor中并不成立。一旦SQL执行一次,在Library Cache中会去生成shared cursor,何来三次之说?应该说,这个论断前提是Private Cursor共享。
2、软软解析和参数配置
我们接触很多的概念是“Hard Parse”和“Soft Parse”。两者的差异在于是否在Library Cache中发生执行计划生成的动作。如果我们将Private Cursor因素考虑进去之后,就会有一个新的解析类型“软软解析”。
即使是Soft Parse,我们在PGA里面,每次执行SQL的时候都会有Private Cursor的创建过程。按照Cursor生命周期,当Cursor执行结束之后,会有一个Close动作将Private Cursor失效。Oracle是可以尝试对Private Cursor进行缓存,也就是说,Close动作并不是真正关闭消失,而是可以支持共享Private Cursor。
如果可以实现Private Cursor在PGA中的重用,我们是可以将PGA中创建Cursor的部分成本消除掉。实现所谓的软软解析。
从Oracle早期开始,我们接触过一个参数为open_cursor。最初这个参数起到两个层面作用,其一是控制一个会话可以同时打开的最大Cursor数量,另一个是控制了PGA里面能够共享Private Cursor缓存的最大个数。
之后Oracle的设置出现了一些变化,引入了新参数session_cached_cursors,单独进行缓存区大小的限制。目前笔者实验的版本中,这个参数是50。
SQL> show parameter cached
NAME TYPE VALUE
------------------------------------ ----------- ---------------
session_cached_cursors integer 50
对于PGA里面的Private Cursor共享情况,Oracle会记录生成的次数。当执行三次的时候,就会建立PGA内部缓存的结构机制。
本篇中我们使用10046来验证上面提到的机制。
3、环境准备
我们先找一个10046的Trace文件作为实验对象。选择11.2.0.3作为实验对象。
SQL> select value from v$diag_info where name='Default Trace File';
VALUE
--------------------------------------------------------------------
/u01/app/diag/rdbms/ora11g/ora11g/trace/ora11g_ora_6964.trc
清空shared pool和buffer cache,执行相同的SQL语句10次。
SQL> alter system flush shared_pool;
系统已更改。
SQL> alter system flush buffer_cache;
系统已更改。
SQL> alter session set events '10046 trace name context forever, level 12';
会话已更改。
SQL> select count(*) from t;
COUNT(*)
----------
0
SQL> select count(*) from t;
COUNT(*)
----------
0
(其余执行次数略……)
SQL> alter session set events '10046 trace name context off';
会话已更改。
SQL>
生成了trace文件之后,我们下面详细分析这个文件的细节。
4、Trace RAW解析
Trace RAW文件记录了SQL执行过程中所有的细节内容,包括等待事件和执行计划。RAW文件重要之处在于其中包括了很多Recursive Call SQL细节,帮助我们分析每个步骤究竟发生了什么。
我们依次对10次执行相同SQL的过程进行分析和解释。首先是第一次SQL语句解析出现。
--第一次执行片段(开始部分)
=====================
PARSING IN CURSOR #106410908 len=22 dep=0 uid=0 ct=3 lid=0 tim=1382941437045398 hv=2763161912 ad='2e33bcc0' sqlid='cyzznbykb509s'
select count(*) from t
END OF STMT
PARSE #106410908:c=28996,e=28852,p=291,cr=85,cu=0,mis=1,r=0,dep=0,og=1,plh=2966233522,tim=1382941437045395
EXEC #106410908:c=0,e=36,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=2966233522,tim=1382941437045585
WAIT #106410908: nam='SQL*Net message to client' ela= 6 driver id=1413697536 #bytes=1 p3=0 obj#=78127 tim=1382941437045636
注意几个细节:首先我们说中间的PARISING IN CURSOR表示Oracle开始启动解析过程,这个步骤SQL还没有被解析。SQL语句被赋予了一个数字编号:#106410908。这个时候,SQL语句还是文本样式。
在PARSE步骤,Oracle实现了语句的Parse,注意,无论是Hard还是Soft Parse,都会有这个步骤和资源消耗,只是多少之分。其中mis=1>0,表示Oracle在Library Cache中没有找到对应的共享游标,于是只能重新进行Parse解析。这是一次硬解析过程,消耗时间为28852(e=28852)。
对这个mis,我们要注意一下,即使不是Parse,也有可能出现mis>1的情况。这个时候,说明缓存的共享游标(shared pool)被age out出去,Oracle在执行中间需要重新进行硬解析步骤。
上面就是第一次执行的开始阶段,之后就是读块的动作,最后,我们看到了PGA里面Private Cursor关闭动作。
*** 2013-10-28 14:23:58.135
WAIT #106410908: nam='SQL*Net message from client' ela= 1074561 driver id=1413697536 #bytes=1 p3=0 obj#=78127 tim=1382941438135076
CLOSE #106410908:c=0,e=64,dep=0,type=0,tim=1382941438135689
关闭动作时体现PGA里面Private Cursor的表现。Shared Cursor只有Age out、Flush的情况,而Private Cursor有关闭的动作,就如同我们在代码里面显示的进行cursor close。
注意里面有一个重要取值type,type就用于表示关闭游标的操作类型。取值含义如下:
ü 0:表示游标从未被缓存且执行次数小于3次;
ü 1:该游标从未被缓存但是执行次数至少3次,如果此时PGA中还有缓存位置,也就是数量没有超过session_cached_cursor参数上线,就会被缓存;
ü 2:该游标从未被缓存但是执行次数至少3次,如果要进入缓存,需要将原有的旧缓存age out出去,注意是将PGA里面的缓存age out;
ü 3:已经被缓存,交由缓存管理;
上面的取值规则,反映了Private Cursor的工作原则。第一次执行关闭动作type为0,表示没有进入缓存,执行次数小于3次。
下面是第二次执行情况。
--第二次执行
=====================
PARSING IN CURSOR #106410908 len=22 dep=0 uid=0 ct=3 lid=0 tim=1382941438136083 hv=2763161912 ad='2e33bcc0' sqlid='cyzznbykb509s'
select count(*) from t
END OF STMT
PARSE #106410908:c=0,e=145,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=2966233522,tim=1382941438136079
EXEC #106410908:c=0,e=95,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=2966233522,tim=1382941438136254
WAIT #106410908: nam='SQL*Net message to client' ela= 7 driver id=1413697536 #bytes=1 p3=0 obj#=78127 tim=1382941438136300
第二次执行中依然有PARSING IN CURSOR动作和PARSE动作。形成了Cursor编号依然是106410908。但是注意PARSE部分中,mis=0,说明此时发生了Shared Cursor在Library Cache中的共享。而且从时间上看,e=145,远远小于第一次执行的消耗值。这次典型是一个Soft Parse动作。
第二次关闭游标记录。
*** 2013-10-28 14:23:59.071
WAIT #106410908: nam='SQL*Net message from client' ela= 926273 driver id=1413697536 #bytes=1 p3=0 obj#=78127 tim=1382941439071316
CLOSE #106410908:c=0,e=60,dep=0,type=0,tim=1382941439072000
第二次关闭cursor时候,type依然为0,由于是第二次,没有达到门限值。
第三次执行SQL,开头情况如下:
=====================
PARSING IN CURSOR #106410908 len=22 dep=0 uid=0 ct=3 lid=0 tim=1382941439072341 hv=2763161912 ad='2e33bcc0' sqlid='cyzznbykb509s'
select count(*) from t
END OF STMT
PARSE #106410908:c=0,e=126,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=2966233522,tim=1382941439072337
EXEC #106410908:c=0,e=100,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=2966233522,tim=1382941439072516
依然是存在PARSING IN CURSOR和PARSE动作。PARSE中的mis取值为0,时间e=126。这次依然是shared cursor中的软解析动作。
第三次关闭cursor情况如下:
*** 2013-10-28 14:23:59.871
WAIT #106410908: nam='SQL*Net message from client' ela= 789009 driver id=1413697536 #bytes=1 p3=0 obj#=78127 tim=1382941439871440
CLOSE #106410908:c=0,e=141,dep=0,type=1,tim=1382941439872393
Close动作中,type取值出现为1。根据注释信息,type=1表示当前Private Cursor没有在PGA缓存里面,但是已经出现了三次,如果PGA缓存有空间,就会进入缓存中。
第四次执行情况:
--第三次执行的结尾
WAIT #106410908: nam='SQL*Net message from client' ela= 789009 driver id=1413697536 #bytes=1 p3=0 obj#=78127 tim=1382941439871440
CLOSE #106410908:c=0,e=141,dep=0,type=1,tim=1382941439872393
--第四次执行的开头
PARSE #106410908:c=0,e=110,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=2966233522,tim=1382941439872577
EXEC #106410908:c=0,e=109,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=2966233522,tim=1382941439872723
WAIT #106410908: nam='SQL*Net message to client' ela= 59 driver id=1413697536 #bytes=1 p3=0 obj#=78127 tim=1382941439872829
第四次出现了比较大的差异,我们没有找到PARING IN CURSOR,只有Parse动作,mis=0。除了说明进行了软解析之外,也说明此次Private Cursor也是在共享,没有出现重新Parse出Private Cursor的情况,也就是出现“软软解析”。
结尾中的close动作,也是如此表示的。
WAIT #106410908: nam='SQL*Net message from client' ela= 854440 driver id=1413697536 #bytes=1 p3=0 obj#=78127 tim=1382941440735702
CLOSE #106410908:c=0,e=79,dep=0,type=3,tim=1382941440736504
此时的type取值为3,说明当前这个游标已经进入缓存结构,被共享。
此后第五到最后一次的情况均和第四次执行相同,都是没有PARSING IN CURSOR,只有PARSE动作,结尾close游标的时候type=3。具体篇幅原因,代码片段省略。
5、结论
通过上面的实验,我们可以得到关于shared cursor和private cursor的机制。
首先,如果SQL执行中,没有找到对应的shared cursor执行计划,重新生成执行计划,这个过程就是硬解析。一旦执行计划被生成,就可以被共享,不存在三次之说。如果shared pool中存在对应的执行计划shared cursor,执行被称为soft parse软解析。
对于Private Cursor,如果连续执行三次以上,该语句的Private Cursor就可以缓存在PGA里面,以后再次执行的时候就可以进行共享,也就是软软解析。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/17203031/viewspace-775379/ ,如需转载,请注明出处,否则将追究法律责任。