Cursor直译过来就是“游标”,它是Oracle数据库中SQL解析和执行的载体。Oracle数据库是用C语言写的,可以将Cursor理解成是C语言的一种结构(Structure)。
Oracle数据库里的Cursor分为两种类型:一种是Shared Cursor;另一种是Session Cursor。本文先介绍Shared Cursor。
1 Oracle里的Shared Cursor。
1.1 Shared Cursor的含义
Shared Cursor就是指缓存在库缓存里的一种库缓存对象,说白了就是指缓存在库缓存里的SQL语句和匿名PL/SQL语句所对应的库缓存对象。Shared Cursor是Oralce缓存在Library Cache中的几十种库缓存对象之一,它所对应的库缓存对象名柄的Namespace属性的值是CRSR(也就是Cursor的缩写)。Shared Cursor里会存储目标SQL的SQL文本、解析树、该SQL所涉及的对象定义、该SQL所使用的绑定变量类型和长度,以及该SQL的执行计划等信息。
Oracle数据库中的Shared Cursor又细分为Parent Cursor(父游标)和Child Cursor(子游标)这两种类型,我们可以通过分别查询视图V$SQLAREA和V$SQL来查看当前缓存在库缓存中的Parent Cursor和Chile Cursor,其中V$SQLAREA用于查看Parent Cursor,V$SQL用于查看Child Cursor。
Parent Cursor和Child Cursor的结构是一样的(它们都是以库缓存对象名柄的方式缓存在库缓存中,Namespace属性的值均为CRSR),它们的区别在于目标SQL的SQL文本会存储在其Parent Cursor所对应的库缓存对象句柄的属性Name中(Child Cursor对应的库缓存对象名柄的Name属性值为空,这意味着只有通过Parent Cursor才能找到相应的Child Cursor),而该SQL的解析树和执行计划则会存储在其Child Cursor所对应的库缓存对象句柄的Heap 6中,同时Oracle会在该SQL所对应的Parent Cursor的Heap 0的Chhild table中存储从属于该Parent Cursor的所有Child Cursor的库缓存对象名柄地址(这意味着Oracle可以通过访问Parent Cursor的Heap 0中的Child table而依次顺序访问从属于该Parent Cursor的所有Child Cursor)。
这种Parent Cursor和Child Cursor的结构就决定了在Oracle数据库里,任意一个目标SQL一定会同时对应两个Shared Cursor,其中一个是Parent Cursor,另外一个则是Child Cursor,Parent Cursor会存储该SQL的SQL文本,而该SQL真正的可以被重用的解析树和执行计划则存储在Child Cursor中。
Oracle设计这种Parent Cursor和Child Cursor并存的结果是因为Oralce是根据目标SQL的SQL文本的哈希值去相应Hash Bucket中的库缓存对象句柄链表里找匹配的库缓存对象句柄的,但是不同的SQL文本对应的哈希值可能相同,而且同一个SQL(此时的哈希值自然是相同的)也有可能有多份不同的解析权和执行计划。可以想象一下,如果它们都处于同一个Hash Bucket中的库缓存对象句柄链表里,那么这个库缓存对象句柄的长度就不是最优的长度(这意味着会增加Oracle从头到尾搜索这个库缓存对象句柄链表所需要耗费的时间和工作量),为了能尽量减少对应Hash Bucket中库缓存对象句柄链表的长度,Oracle设计了这种嵌套的Parent Cursor和Child Cursor并存的结构。
下面看一个Parent Cursor和Child Cursor的实例:
sys@MYDB>conn zx/zx
Connected.
zx@MYDB>select empno,ename from emp;
EMPNO ENAME
---------- ------------------------------
7369 SMITH
......省略部分输出
14 rows selected.
当一条SQL第一次被执行的时候,Oracle会同时产生一个Parent Cursor和一个Child Cursor。上述SQL是首次执行,所以现在Oracle应该会同时产生一个Parent Cursor和一个Child Cursor。使用如下语句验证:
select sql_text,sql_id,version_count from v$sqlarea where sql_text like 'select empno,ename%';
注意到原目标SQL在V$SQLAREA中只有一条匹配记录,且这条记录的列VERSION_COUNT的值为1(VERSION_COUNT表示这个Parent Cursor所拥有的所有Child Cursor的数量),这说明Oracle在执行目标SQL时确实产生了一个Parent Cursor和一个Child Cursor。
上述SQL所对应的SQL_ID为“78bd3uh4a08av”,用这个SQL_ID就可以去V$SQL中查询该SQL对应的所有Child Cursor的信息:
zx@MYDB>col sql_text for a50
zx@MYDB>select sql_text,sql_id,version_count from v$sqlarea where sql_text like 'select empno,ename%';
SQL_TEXT SQL_ID VERSION_COUNT
-------------------------------------------------- --------------------------------------- -------------
select empno,ename from emp 78bd3uh4a08av 1
注意到目标SQL_ID在V$SQL中只有一条匹配记录,而且这条记录的CHILD_NUMBER的值为0(CHILD_NUMBER表示某个Child Cursor所对应的子游标号),说明Oracle在执行原目标SQL时确实只产生了一个子游标号为0的Child Cursor。
把原目标SQL中的表名从小写换成大写的EMP后再执行: