合 Oracle中的游标、硬解析、软解析、软软解析、解析失败
游标简介
在介绍游标之前先介绍一下Oracle数据库中库缓存(Library Cache)的作用及其组成结构。库缓存是SGA中共享池(Shared Pool)中的一块内存区域,主要作用就是缓存执行过的SQL语句和PL/SQL语句(例如存储过程、函数、包、触发器)及其所对应的解析树(Parse Tree)和执行计划等信息。当同样的SQL语句和PL/SQL语句再次被执行的时候就可以直接利用已经缓存在库缓存中的那些相关对象而无须再次从头开始解析,这样就提高了这些SQL语句和PL/SQL语句在重复执行时的执行效率。
库缓存(Library Cache)在SGA中的位置如下图所示:
接下来需要明白如下几个概念:
- 库缓存对象(Library Cache Object,LCO):缓存在库缓存中的对象被称之为库缓存对象,包括SQL语句、PL/SQL语句、表、索引、包、触发器等。可以使用视图
V$DB_OBJECT_CACHE
(基表为X$KGLOB
)来查询当前缓存在库缓存中的所有对象。 - 库缓存对象句柄(Library Cache Object Handle):所有的库缓存对象都是以一种名为库缓存对象句柄的结构存储在库缓存中,Oracle是通过访问相关的库缓存对象句柄来访问对应的库缓存对象的。所以,要访问库缓存对象,就需要先访问库缓存对象句柄。库缓存对象句柄是Oracle自定义的一种复杂的C语言结构。由于库缓存对象句柄是以哈希表(Hash Table)的方式存储在库缓存中的,哈希表就是很多Hash Bucket组成的数组,所以,Oracle会通过相关的哈希运算来存储和访问对应的库缓存对象句柄。
整个库缓存的组成结构如下图所示:
从上图可以看出,整个库缓存可以看作是由一组Hash Bucket所组成,每一个Hash Bucket都对应不同的哈希值(Hash Value)。对于单个Hash Bucket而言,里面存储的就是哈希值相同的所有库缓存对象句柄,同一个Hash Bucket中不同的库缓存对象句柄之间会用指针连接起来,即同一个Hash Bucket中不同的库缓存对象句柄之间实际上组成了一个库缓存对象句柄链表(Library Cache Object Handles)。
当Oracle要执行目标SQL“select * from lhr.emp”时,首先会对该SQL的SQL文本进行哈希运算,然后根据得到的哈希值找到相关的Hash Bucket,在Hash Bucket中遍历对应的库缓存对象句柄链表。如果找到了对应的库缓存对象句柄,那么就可以直接访问到该SQL的执行计划、解析树等对象,这意味着可以直接重用这些对象而无须再次从头开始解析:如果找不到对应的库缓存对象句柄,那么意味着必须从头开始解析,并且把解析后的执行计划、解析树等对象以库缓存对象句柄的方式链接在相关的Hash Bucket中的库缓存对象句柄链表中。
由于库缓存对象句柄是Oracle自定义的一种复杂的C语言结构,所以,库缓存对象句柄有很多属性,每一个属性都有其特定的作用,这里介绍Name、Namespace和Heap 0 Pointer这3个属性。
- 属性“Name”表示的是库缓存对象句柄所对应的库缓存对象的名称。例如,如果库缓存对象是SQL语句,那么属性“Name”的值就是该SQL的SQL文本:如果库缓存对象是表,那么属性“Name”的值就是该表的表名。
- 属性“Namespace”表示的是库缓存对象句柄对应的库缓存对象所在的分组名,不同类型的库缓存对象可能属于同一个分组,即不同类型的库缓存对象所对应的库缓存对象句柄的Namespace值有可能是相同的。例如,SQL语句和匿名PL/SQL块所对应的库缓存对象句柄的Namespace的值就都是CRSR。Oracle数据库中常见的Namespace的值和其对应的含义如下表所示:
Namespace值 | 含义 |
---|---|
CRSR | SQL语句和匿名PL/SQL块所对应的库缓存对象句柄的Namespace的值均为“CRSR” |
TABL/PRCD/TYPE | 表、视图、序列、同义词、存储过程、函数、Type和Package的定义所对应的库缓存对象句柄的Namespace的值均为“TABL/PRCD/TYPE” |
BODY/TYBD | Type和Package的具体实现(Body)所对应的库缓存对象句柄的Namespace的值均为“BODY/TYBD” |
TRGR | 触发器所对应的库缓存对象句柄的Namespace的值为“TRGR” |
INDX | 索引所对应的厍缓存对象句柄的Namespace的值为“INDX” |
CLST | Cluster所对应的库缓存对象句柄的Namespace的值为“CLST” |
属性“Heap 0 Pointer”是指向子结构Heap 0的指针,是一种嵌套结构。Heap 0的结构较为复杂,它有很多属性,每一个属性都有其特定的作用,这里介绍Tables和Data Blocks Pointer这两个属性。
① 属性“Tables”里记录的是与该Heap 0所在的库缓存对象有关联关系的库缓存对象句柄地址的集合。“Tables”又细分为很多类(从Dependency table到Schema name table),其中最值得关注的就是“Child table”。“Child table”里记录的就是从属于该Heap 0所在的库缓存对象的子库缓存对象的句柄地址的集合。Heap 0里的“Tables”实际上记录的就是各个库缓存对象之间的关联关系,Oracle可以通过这些关联关系直接访问到对应的库缓存对象。例如,Oracle可以通过访问某个库缓存对象的Heap 0中的Child table而依次顺序访问从属于该对象的所有子库缓存对象。
② 对每一个库缓存对象而言,都或多或少需要往库缓存中存储一些该库缓存对象所特有的动态运行时(runtime)数据,比如SQL语句所对应的库缓存对象就需要在库缓存中缓存该SQL语句所对应的编译好的二进制格式的执行计划。Oracle会用Data Heap来存储这些动态运行时数据,所谓的“Data Heap”,可以简单地理解成是库缓存中的一块连续的内存区域,Data Heap是动态分配的,其大小并不固定。每一个库缓存对象都可能会拥有多个Data Heap,这里分别将它们命名为Heap 1、Heap 2、……、Heap n。各个Data Heap之间是独立的,没有关联关系,Oracle会在Heap 0的属性“Data Blocks Pointer”中存储指向这些Data Heap的指针,这样Oracle通过访问Heap 0就可以按需访问该Heap 0所在的库缓存对象拥有的所有Data Heap了。
游标的分类及共享游标
游标的分类
游标(Cursor)是Oracle数据库中SQL解析和执行的载体,它可以分为共享游标(Shared Cursor)和会话游标(Session Cursor)。共享游标是指缓存在库缓存(Library Cache)里的一种库缓存对象,其实就是指缓存在库缓存里的SQL语句和匿名PL/SQL块所对应的库缓存对象。共享游标是Oracle缓存在库缓存中的几十种库缓存对象之一,它所对应的库缓存对象句柄的Namespace属性的值是CRSR(也就是Cursor的缩写)。共享游标会存储目标SQL的SQL文本、解析树、该SQL所涉及的对象定义、该SQL所使用的绑定变量类型和长度,以及该SQL的执行计划等信息。共享游标可以细分为父游标(Parent Cursor)和子游标(Child Cursor),可以通过视图V$SQLAREA来查看当前缓存在库缓存(Library Cache)中的父游标,而通过V$SQL来查看缓存在库缓存中的子游标。Oracle设计这种嵌套的Parent Cursor和Child Cursor并存的结构是为了能尽量减少对应的Hash Bucket中库缓存对象句柄链表的长度。
Oracle中游标的分类如下所示:
图 3-13 Oracle中的游标分类
父游标和子游标的对比如下表所示:
Oracle在解析目标SQL时去库缓存中查找匹配Shared Cursor的过程如下图所示:
从上图可以看出,Oracle在解析目标SQL时去库缓存中查找匹配共享游标(Shared Cursor)的过程包含如下几个步骤:
(1)根据目标SQL的SQL文本的哈希值去库缓存中查找匹配的Hash Bucket。
(2)然后在匹配的Hash Bucket的库缓存对象链表中查找匹配的父游标(Parent Cursor),当然,在查找匹配Parent Cursor的过程中肯定会比对目标SQL的SQL文本(因为不同的SQL文本计算出来的哈希值可能是相同的)。
(3)在步骤(2)中,如果找到了匹配的父游标,那么Oracle接下来就会遍历从属于该父游标的所有子游标以查找匹配的子游标。如果找不到匹配的父游标,那么也意味着此时没有可以共享的解析树和执行计划,Oracle就会从头开始解析上述目标SQL,新生成一个父游标和一个子游标,并把它们挂在对应的Hash Bucket中。在匹配的父游标中查找匹配的子游标时,如果找到了匹配的子游标,那么Oracle就会把存储于该子游标中的解析树和执行计划直接拿过来重用,而不用再从头开始解析。如果找不到匹配的子游标,那么意味着没有可以共享的解析树和执行计划,接下来Oracle也会从头开始解析上述目标SQL,新生成一个子游标,并把这个子游标挂在对应的父游标下。
会话游标
(一)会话游标的含义
会话游标(Session Cursor)是当前会话(Session)解析和执行SQL的载体,即会话游标用于在当前会话(Session)中解析和执行SQL,会话游标缓存在PGA中(Shared Cursor是缓存在SGA的库缓存里)。会话游标是以哈希表的方式缓存在PGA中,在缓存会话游标的哈希表的对应Hash Bucket中,Oracle会存储目标SQL对应的父游标的库缓存对象句柄地址,所以,Oracle可以通过会话游标找到对应的父游标,进而就可以找到对应子游标中目标SQL的解析树和执行计划,然后Oracle就可以按照这个解析树和执行计划来执行目标SQL了。
会话游标在目标SQL的执行过程中实际上起到了一个承上启下的作用,Oracle依靠会话游标来将目标SQL所涉及的数据从Buffer Cache的对应数据块读到PGA里,然后在PGA里做后续的处理(如排序、表连接等),最后将最终的处理结果返回给用户,所以,会话游标是当前会话解析和执行SQL的载体的原因。
关于会话游标,需要注意以下几点:
① 会话游标(Session Cursor)与会话(Session)是一一对应的,不同会话的会话游标之间不能共享,这是与共享游标(Shared Cursor)的本质区别。
② 会话游标是有生命周期的,每个会话游标在使用的过程中都至少会经历一次Open、Parse、Bind、Execute、Fetch和Close中的一个或多个阶段,用过的会话游标不一定会缓存在对应会话的PGA中,这取决于参数SESSION_CACHED_CURSORS的值是否大于0。
共享游标和会话游标的对比如下表所示:
(二)会话游标的分类
会话游标的详细分类参考下表:
表 3-20 Oracle中会话游标的分类
在上表中特别说明一下动态游标,动态游标是Oracle数据库中最灵活的一种会话游标,它的灵活性表现在:①动态游标的定义方式非常灵活,它可以有多种定义方式。②参考游标可以作为存储过程的输入参数和函数的输出参数。上表中的各种游标希望读者可以通过做大量的练习题来掌握,毕竟游标是存储过程开发过程中必不可少的内容。
(三)会话游标的属性
会话游标有4个属性,见下表:
表 3-21 游标的属性
当执行一条DML语句后,DML语句的结果保存在这四个游标属性中,这些属性用于控制程序流程或者了解程序的状态。当运行DML语句时,PL/SQL打开一个内建游标并处理结果。在这些属性中,SQL%FOUND和SQL%NOTFOUND是布尔值,SQL%ROWCOUNT是整数值。需要注意的是,若游标属于隐式游标,则在PL/SQL中可以直接使用上表中的属性,若游标属于显式游标,则上表中的属性里“SQL%”需要替换为自定义显式游标的名称。上表中的这4个属性对于动态游标依然适用。
(四)会话游标的相关参数
和会话游标相关的有2个重要参数,分别为OPEN_CURSORS和SESSION_CACHED_CURSORS,下面详细介绍这两个参数。
(1)参数OPEN_CURSORS用于设定单个会话中同时能够以OPEN状态并存的会话游标的总数,默认值为50。若该值为300,则表示单个会话中同时能够以OPEN状态并存的会话游标的总数不能超过300,否则Oracle会报错“ORA-1000:maximum open cursors exceeded”。视图V$OPEN_CURSOR可以用来查询数据库中状态为OPEN或者己经被缓存在PGA中的会话游标的数量和具体信息(例如,SQL_ID和SQL文本等)。当然,也可以从视图V$SYSSTAT中查到当前所有以OPEN状态存在的会话游标的总数。
1 2 3 4 5 6 7 8 9 | LHR@orclasm > show parameter open_cursors NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ open_cursors integer 65535 SELECT USERENV('SID') FROM DUAL; SELECT * FROM V$OPEN_CURSOR WHERE SID=16; SELECT * FROM V$SYSSTAT D WHERE D.NAME ='opened cursors current'; |
(2)参数SESSION_CACHED_CURSORS用于设定单个会话中能够以Soft Closed状态并存的会话游标的总数,即用于设定单个会话能够缓存在PGA中的会话游标的总数。在Oracle 10g中默认为20(注意:在官方文档中该值默认为0,其实是20),11g中默认为50。
1 2 3 4 5 | LHR@orclasm > show parameter session_cached_cursors NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ session_cached_cursors integer 50 |
从上述显示结果可以看出,SESSION_CACHED_CURSORS的值为50,意味着在这个库里,单个会话中同时能够以Soft Closed状态缓存在PGA中的会话游标的总数不能超过50。
关于参数SESSION_CACHED_CURSORS需要注意以下几点: