游标简介
在介绍游标之前先介绍一下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状态存在的会话游标的总数。
| 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。
| 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需要注意以下几点:
本人提供Oracle(OCP、OCM)、MySQL(OCP)、PostgreSQL(PGCA、PGCE、PGCM)等数据库的培训和考证业务,私聊QQ646634621或微信dbaup66,谢谢!
① Oracle会用LRU算法来管理这些已缓存的会话游标(从会话游标的dump文件中可以证实这一点),所以即便某个Session以Soft Closed状态缓存在PGA中的会话游标的总数己经达到了SESSION_CACHED_CURSORS所设置的上限也没有关系,LRU算法依然能够保证那些频繁反复执行的SQL所对应的会话游标的缓存命中率要高于那些不频繁反复执行的SQL。
① 在Oracle 11gR2中,一个会话游标能够被缓存在PGA中的必要条件是该会话游标所对应的SQL解析和执行的次数要超过3次。Oracle这么做的目的是为了避免那些执行次数很少的SQL所对应的会话游标也被缓存在PGA里,这些SQL很可能只执行一次而且不会重复执行,所以把这些执行次数很少的SQL所对应的会话游标缓存在PGA中是没有太大意义的。可以使用如下的SQL语句查询缓存在当前系统中的所有会话游标:
| SELECT D.INST_ID, D.SQL_ID,D.SQL_TEXT,D.SID,D.USER_NAME,D.HASH_VALUE FROM GV$OPEN_CURSOR D WHERE D.CURSOR_TYPE='SESSION CURSOR CACHED'; |
下面给出一个会话游标缓存的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | LHR@orclasm > alter system flush shared_pool;--生产库慎用 System altered. 开始第1次执行 LHR@orclasm > SELECT D.SQL_ID,D.CURSOR_TYPE FROM V$OPEN_CURSOR D WHERE D.SID=USERENV('SID') AND D.SQL_TEXT LIKE 'SELECT /*test scc*/ COUNT(*)%' ; no rows selected LHR@orclasm > SELECT /*test scc*/ COUNT(*) FROM SCOTT.EMP; COUNT(*) ---------- 14 LHR@orclasm > SELECT D.SQL_ID,D.CURSOR_TYPE FROM V$OPEN_CURSOR D WHERE D.SID=USERENV('SID') AND D.SQL_TEXT LIKE 'SELECT /*test scc*/ COUNT(*)%' ; no rows selected 开始第2次执行: LHR@orclasm > SELECT /*test scc*/ COUNT(*) FROM SCOTT.EMP; COUNT(*) ---------- 14 LHR@orclasm > SELECT D.SQL_ID,D.CURSOR_TYPE FROM V$OPEN_CURSOR D WHERE D.SID=USERENV('SID') AND D.SQL_TEXT LIKE 'SELECT /*test scc*/ COUNT(*)%' ; no rows selected 开始第3次执行: LHR@orclasm > SELECT /*test scc*/ COUNT(*) FROM SCOTT.EMP; COUNT(*) ---------- 14 LHR@orclasm > SELECT D.SQL_ID,D.CURSOR_TYPE FROM V$OPEN_CURSOR D WHERE D.SID=USERENV('SID') AND D.SQL_TEXT LIKE 'SELECT /*test scc*/ COUNT(*)%' ; SQL_ID CURSOR_TYPE ------------- ---------------------------------------------------------------- 9r01dt51f46tf DICTIONARY LOOKUP CURSOR CACHED 从结果可以看到,虽然已经缓存到PGA中了,但是类型为“DICTIONARY LOOKUP CURSOR CACHED”,并不是“SESSION CURSOR CACHED”,所以下面开始第4次执行: LHR@orclasm > SELECT /*test scc*/ COUNT(*) FROM SCOTT.EMP; COUNT(*) ---------- 14 LHR@orclasm > SELECT D.SQL_ID,D.CURSOR_TYPE FROM V$OPEN_CURSOR D WHERE D.SID=USERENV('SID') AND D.SQL_TEXT LIKE 'SELECT /*test scc*/ COUNT(*)%' ; SQL_ID CURSOR_TYPE ------------- ---------------------------------------------------------------- 9r01dt51f46tf SESSION CURSOR CACHED LHR@orclasm > SELECT a.VERSION_COUNT,a.EXECUTIONS,a.PARSE_CALLS,a.LOADS FROM v$sqlarea a WHERE a.SQL_ID='9r01dt51f46tf'; VERSION_COUNT EXECUTIONS PARSE_CALLS LOADS ------------- ---------- ----------- ---------- 1 4 3 1 从结果可以看到,在SQL语句“SELECT /*test scc*/ COUNT(*) FROM SCOTT.EMP;”第4次执行完毕后,Oracle已经将其对应的会话游标缓存在当前会话的PGA中了,而此时缓存的会话游标的类型为“SESSION CURSOR CACHED”。下面开始第5次执行: LHR@orclasm > SELECT /*test scc*/ COUNT(*) FROM SCOTT.EMP; COUNT(*) ---------- 14 LHR@orclasm > SELECT D.SQL_ID,D.CURSOR_TYPE FROM V$OPEN_CURSOR D WHERE D.SID=USERENV('SID') AND D.SQL_TEXT LIKE 'SELECT /*test scc*/ COUNT(*)%' ; SQL_ID CURSOR_TYPE ------------- ---------------------------------------------------------------- 9r01dt51f46tf SESSION CURSOR CACHED LHR@orclasm > SELECT a.VERSION_COUNT,a.EXECUTIONS,a.PARSE_CALLS,a.LOADS FROM v$sqlarea a WHERE a.SQL_ID='9r01dt51f46tf'; VERSION_COUNT EXECUTIONS PARSE_CALLS LOADS ------------- ---------- ----------- ---------- 1 5 3 1 |
从结果看出,缓存的会话游标的类型依然为“SESSION CURSOR CACHED”,不再改变。
(五)会话游标的dump文件
会话游标的dump文件可以通过Level值为3的errorstack得到,获取过程如下所示:
| SELECT COUNT(*) FROM SCOTT.EMP;--执行5次,让其缓存在PGA中 ALTER SESSION SET EVENTS 'IMMEDIATE TRACE NAME ERRORSTACK LEVEL 3'; SELECT COUNT(*) FROM SCOTT.EMP; ALTER SESSION SET EVENTS 'IMMEDIATE TRACE NAME ERRORSTACK OFF'; SELECT VALUE FROM V$DIAG_INFO; |
SQL的解析过程(硬解析、软解析、软软解析)
在Oracle中,每条SQL语句在执行之前都需要经过解析(Parse)。DDL语句是从来不会共享使用的,也就是说DDL语句每次执行都需要进行硬解析。但是,DML语句和SELECT语句会根据情况选择是进行硬解析,还是进行软解析或者进行软软解析。
Oracle对SQL的处理过程如下所示:
(1)语法检查(Syntax Check):检查此SQL的拼写是否正确。
(2)语义检查(Semantic Check):例如检查SQL语句中的访问对象是否存在及该用户是否具备相应的权限。
(3)对SQL语句进行解析(Parse):利用内部算法对SQL进行解析,生成解析树(Parse Tree)及执行计划(Execution Plan)。
(4)执行SQL,返回结果。
解析主要分为3种类型:硬解析(Hard Parse)、软解析(Soft Parse)和软软解析(Soft Soft Parse,也叫快速解析(Fast Parse)),它们的解析过程可以参考下图:

Oracle在解析和执行目标SQL时,会先去当前会话的PGA中查找是否存在匹配的缓存会话游标(Session Cursor)。当Oracle第一次解析和执行目标SQL时(显然是硬解析),当前会话的PGA中肯定不存在匹配的会话游标,这时Oracle会新生成一个会话游标和一对共享游标(包含一个父游标和一个子游标),这其中的共享游标会存储能被所有会话共享、重用的内容(比如目标SQL的解析树、执行计划等),而会话游标则会经历一次Open、Parse、Bind、Execute、Fetch和Close中的一个或多个阶段。
对会话游标(Session Cursor)和共享游标(Shared Cursor)之间的关联关系如下总结:
- 无论是硬解析、软解析还是软软解析,Oracle在解析和执行目标SQL时,始终会先去当前会话(Session)的PGA中寻找是否存在匹配的缓存会话游标。
- 如果在当前会话的PGA中找不到匹配的缓存会话游标,那么Oracle就会去SGA的库缓存(Library Cache)中查找是否存在匹配的父游标。如果在库缓存中找不到匹配的父游标,那么Oracle就会新生成一个会话游标和一对共享游标(即父游标和子游标);如果找到了匹配的父游标,但找不到匹配的子游标,那么Oracle就会新生成一个会话游标和一个子游标(这个子游标会被挂在之前找到的匹配父游标下)。无论哪一种情况,这两个过程对应的都是硬解析。
- 如果在当前会话的PGA中找不到匹配的缓存会话游标,但在库缓存中找到了匹配的父游标和子游标,那么Oracle会新生成一个会话游标并重用刚刚找到的匹配父游标和子游标,这个过程对应的就是软解析。
- 如果在当前会话的PGA中找到了匹配的缓存会话游标,那么此时Oracle就不再需要新生成一个会话游标,并且也不再需要像软解析那样得去SGA的库缓存中查找匹配的父游标了,因为Oracle此时可以重用找到的匹配会话游标,并且可以通过这个会话游标直接访问到该SQL对应的父游标,这个过程就是软软解析。
硬解析(Hard Parse)、软解析(Soft Parse)和软软解析(Soft Soft Parse)的比对参考下表:

下面详解介绍硬解析(Hard Parse)、软解析(Soft Parse)和软软解析(Soft Soft Parse):
(一)硬解析(Hard Parse)
硬解析(Hard Parse)是指Oracle在执行目标SQL时,在库缓存(Library Cache)中找不到可以重用的解析树和执行计划,而不得不从头开始解析目标SQL并生成相应的父游标(Parent Cursor)和子游标(Child Cursor)的过程。
硬解析实际上有两种类型:一种是在库缓存中找不到匹配的父游标(Parent Cursor),此时Oracle会从头开始解析目标SQL,新生成一个父游标和一个子游标,并把它们挂在对应的HashBucket中;另外一种是找到了匹配的父游标但未找到匹配的子游标,此时Oracle也会从头开始解析该目标SQL,新生成一个子游标,并把这个子游标挂在对应的父游标下。
硬解析大致可以分为5个执行步骤:
(1)语法分析。
(2)权限与对象检查。
(3)在共享池中检查是否有完全相同的之前完全解析好的。如果存在,则直接跳过步骤(4)和步骤(5),运行SQL,此时算SOFT PARSE。
(4)选择执行计划。
(5)产生执行计划。
需要注意的是,创建解析树、生成执行计划对于SQL的执行来说是开销昂贵的动作,所以,应当极力避免硬解析,尽量使用软解析。这就是在很多项目中,倡导开发设计人员对功能相同的代码要努力保持代码的一致性,以及要在程序中多使用绑定变量的原因。
在硬解析时,需要申请闩的使用,而闩的数量在有限的情况下需要等待。大量的闩的使用由此造成需要使用闩的进程排队越频繁,性能则逾低下。具体来说,硬解析的危害性体现在以下几点上:
① 硬解析可能会导致Shared Pool Latch的争用。无论是哪种类型的硬解析,都至少需要新生成一个Child Cursor,并把目标SQL的解析树和执行计划载入该Child Cursor里,然后把这个Child Cursor存储在库缓存中。这意味着Oracle必须在Shared Pool中分配出一块内存区域用于存储上述Child Cursor,而在Shared Pool中分配内存这个动作是要持有Shared Pool Latch的(Oracle数据库中Latch的作用之一就是保护共享内存的分配),所以如果有一定数量的并发硬解析,可能就会导致Shared Pool Latch的争用,而一旦发生大量的Shared Pool Latch争用,系统的性能和可扩展性是会受到严重影响的(常常表现为CPU的占用率居高不下,接近100%)。
② 硬解析可能会导致库缓存相关Latch(如Library Cache Latch)和Mutex的争用。无论是哪种类型的硬解析,都需要扫描相关Hash Bucket中的库缓存对象句柄链表,而扫描库缓存对象句柄链表这个动作是要持有Library Cache Latch的(Oracle数据库中Latch的另外一个作用就是用于共享SGA内存结构的并发访问控制),所以如果有一定数量的并发硬解析,则也可能会导致Library Cache Latch的争用。和Shared Pool Latch一样,一旦发生大量的Library Cache Latch的争用,系统的性能和可扩展性也会受到严重影响。这里需要注意的是,从11gR1开始,Oracle用Mutex替换了库缓存相关Latch,所以在Oracle 11gR1及其后续的版本中,将不再存在库缓存相关Latch的争用,取而代之的是Mutex的争用(你可以简单地将Mutex理解成是一种轻量级的Latch,Mutex主要也是用于共享SGA内存结构的并发访问控制),Oracle也因此引入了一系列新的等待事件来描述这种Mutex的争用,比如“Cursor:pinS”、“Cursor:pinX”、“Cursor:pin S wait on X”、"Cursor:mutex S'、“Cursor:mutex X”、“Library cache:mutex X”等。
正是因为大量的硬解析可能会导致Shared Pool Latch、库缓存相关Latch/Mutex的争用,进而会严重影响系统的性能和可扩展性,所以才有“硬解析是万恶之源”这样的说法,但实际上,这种说法是不准确的。硬解析是非常不好,它的危害性也有目共睹,但硬解析是否会对系统造成损坏实际上取决于系统的类型,对于高并发的OLTP类型的系统而言,硬解析确实会严重影响系统的性能和可扩展性;但对于OLAP/DSS类型的系统而言,并发的数量很少,目标SQL也很少被并发重复执行,而且在执行目标SQL时硬解析所耗费的时间和资源与该SQL总的执行时间和资源消耗相比是微不足道的,这种情况下用硬解析是没问题的,此时硬解析对系统性能的影响微乎其微,可以忽略不计。所以更为准确的说法应该是一一对于OLTP类型的系统而言,硬解析是万恶之源!
(二)软解析(Soft Parse)
软解析(Soft Parse)是指Oracle在执行目标SQL时,在Library Cache中找到了匹配的父游标(Parent Cursor)和子游标(Child Cursor),并将存储在子游标中的解析树和执行计划直接拿过来重用而无须从头开始解析的过程。和硬解析相比,软解析的优势主要体现在如下这几个方面:
(1)软解析不会导致Shared Pool Latch的争用。因为软解析能够在库缓存中找到匹配的Parent Cursor和Child Cursor,所以它不需要生成新的Parent Cursor和Child Cursor.这意味着软解析根本就不需要持有Shared Pool Latch以便在Shared Pool中申请分配一块共享内存区域,既然不需要持有Shared Pool Latch,自然不会有Shared Pool Latch的争用,即Shared Pool Latch的争用所带来的系统性能和可扩展性的问题对软解析来说并不存在。
(2)软解析虽然也可能会导致库缓存相关Latch(如Library Cache Latch)和Mutex的争用,但软解析持有库缓存相关Latch的次数要少,而且软解析对某些Latch(如Library Cache Latch)的持有时间会比硬解析短,这意味着即使产生了库缓存相关Latch的争用,软解析的争用程度也没有硬解析那么严重,即库缓存相关Latch和Mutex的争用所带来的系统性能和可扩展性的问题对软解析来说要比硬解析少很多。我们在3.1.12节中己经介绍过:硬解析会先持有LibraryCacheLatch,并且在不释放LibraryCacheLatch的情况下持有Shared Pool Latch以便从Shared Pool中申请分配内存,成功申请后就会释放Shared Pool Latch,最后再释放Library Cache Latch。而软解析是不需要持有Shared Pool Latch的,所以与软解析比起来,硬解析持有Library Cache Latch的时间会更长,当然对Library Cache Latch争用的程度就会更严重。
正是基于上述两个方面的原因,如果OLTP类型的系统在执行目标SQL时能够广泛使用软解析,那么系统的性能和可扩展性就会比全部使用硬解析时有显著的提升,执行目标SQL时需要消耗的系统资源(主要体现在CPU上)也会显著降低。
(三)软软解析(Soft Soft Parse)
软软解析(Soft Soft Parse)是指若参数SESSION_CACHED_CURSORS的值大于0,并且该会话游标所对应的目标SQL解析和执行的次数超过3次,则此时该会话游标会被直接缓存在当前会话的PGA中的。若该SQL再次执行的时候,则只需要对其进行语法分析、权限对象分析之后就可以直接从当前会话的PGA中将之前缓存的匹配会话游标直接拿过来用就可以了,这就是软软解析。
当一个SQL语句以硬解析的方式解析和执行完毕后,这个目标SQL所对应的共享游标(Shared Cursor)就己经被缓存在库缓存中,它所对应的会话游标(Session Cursor)也已使用完毕,这时候会根据参数SESSION_CACHED_CURSORS的不同而存在如下这两种情况:
① 如果参数SESSION_CACHED_CURSORS的值等于0,那么会话游标就会正常执行Close操作。在这种情况下,当同一条目标SQL再次重复执行时(显然是软解析),此时是可以找到匹配的共享游标的,但依然找不到匹配的会话游标(因为之前硬解析时对应的会话游标己经被Close掉了),这意味着Oracle还必须为该SQL新生成一个会话游标,并且该会话游标还会再经历一次Open、Parse、Bind、Execute、Fetch和Close中的一个或多个阶段。
② 如果参数SESSION_CACHED_CURSORS的值大于0,并且该会话游标所对应的目标SQL解析和执行的次数超过3次,那么Oracle就不会对会话游标执行Close操作,而是会将其标记为Soft Closed,同时将其缓存在当前会话的PGA中。这样做的好处是,当目标SQL再次被重复执行时,此时共享游标和会话游标就都能够找到匹配记录了,这意味着Oracle己经不需要为该SQL再新生成一个会话游标,而是只需要从当前会话的PGA中将之前己经被标记为Soft Closed的匹配会话游标直接拿过来用就可以了。显然,和软解析比,此时Oracle就省掉了Open一个新的会话游标所需要耗费的资源和时间。另外,Close一个现有会话游标也不需要做了(只需要将其标记为Soft Closed,同时将其缓存在当前会话的PGA中就可以了)。当然,剩下的Parse、Bind、Execute、Fetch还是需要做的,这个过程就是所谓的“软软解析”。
从上述分析过程可以看出,软软解析与软解析比起来,其好处主要体现在如下两个方面:
① 和软解析比,软软解省去了OPEN一个新的会话游标和CLOSE一个现有会话游标所需要耗费的资源和时间。
② 和软解析比,软软解析在持有库缓存相关Latch的次数方面会更少。这是因为缓存在PGA中的会话游标所在的Hash Bucket中己经存储了目标SQL的父游标的库缓存对象句柄地址,Oracle根据这个库缓存对象句柄地址就可以直接去库缓存中访问对应的父游标了,而不再需要先持有库缓存相关Latch,再去库缓存的相应Hash Bucket的父游标所在的库缓存对象句柄链表中查找匹配的父游标了,所以软软解析在持有库缓存相关Latch的次数方面会比软解析要少。
如果要彻底搞懂硬解析、软解析和软软解析,那么请阅读崔华大师的书《基于Oracle的SQL优化》!!!小麦苗把里边的一些重点内容摘抄出来,共享给大家。