初探Oracle里的Cursor

初探Oracle里的Cursor

 

游标(Cursor)

谈起游标首先要谈的就是sga和pga了,先简单讨论一下sga和pga:

SGA:

在SGA中有一个块固定区域Fixed Size ,它包含几千个变量和一些小的数据结构,如 Latch或地址指针等,这部分内存分配和特定的数据库版本以及平台有关,不受用户控制,而且这些信息对于数据库来说非常重要,但是通常我们用户不需要关心。固定部分只需要很小的内存,可以通过一个内部表 X$KSMFSV([K]ernel [S]ervice Layer ,[M]emory Management,Addresses of [F]ixed [S]GA [V]ariables)查询。此外 Oracle 的内部表X$KSMMEM 记录了整个 SGA 的地址映射关系,通过 X$KSMFSV 和 X$KSMMEM 关联,可以找出 Fixed Area 中每个变量的设置。

1.Buffer Cache:Buffer Cache是SGA中一个重要的组件。在Buffer Cache物理层是由很多个内存颗粒组成的内存区域,并且受LRU管理,超出该规则的数据块就会age out,这种会影响Oracle额外的io消耗并且Oracle为此问题提出了多缓冲池技术。多缓冲池技术是指,根据不同数据的不同访问方式,将 Buffer Cache 逻辑层分为 Default、Keep 和 Recycle 池三个部分。对于经常使用的数据,我们可以在建表时就指定将其存放在 Keep池中(Keep 池中的数据倾向于一直保存);对于经常一次性读取使用的数据,可以将其存放在 Recycle 池中(Recycle 池中的数据倾向于即时老化);而 Default 池则存放未指定存储池的数据,按照 LRU 算法管理。

这里对Default池进行讨论,在Buffercache Default池中有LRU、WLRU两个链表用于管理申请Buffer chche中的Buffer,这两个链表(逻辑层)分别用指针指向Buffer Cache中的内存颗粒(物理层)。

LRU list:LRU List 用于维护内存中的 Buffer,按照 LRU 算法进行管理所有的 Buffer 都被 Hash 到 LRU List 上管理。当需要从数据文件上读取数据时,首先要在LRU List上寻找Free-Bucket通过指针找到对应的Free-Buffer,若没有足够的Free-Bucket则通过LRU原则age out部分Bucket使其有足够的Free-Bucket,然后读取数据到选取的Buffer Cache中;当数据被修改之后,之前选取的Free-Bucket状态变为 Dirty,并且移动至 LRUW List,LRUW List 上的都是待被DBWR 写出到数据文件的 Bucket对应的Buffer,一个 Buffer对应的Bucket 要么在 LRU List 上,要么在LRUW List 上存在,不能同时在这两个 List 上存在。

LRUW List:存放更改过的Buffer对应的Bucket,等待被DBWR写出到file中,在数据落盘时同时需要检查点队列,遵循先进先出原则增量落盘。

检查点队列(Checkpoint Queue)则负责按照数据块的修改顺序记录数据块,同时将 RBA和数据块关联起来,这样在进行增量检查点时,数据库可以按照数据块修改的先后顺序将其写出,从而在进行恢复时,可以根据最后写出数据块及其相关的 RBA 开始进行快速恢复。在检查点触发时 DBWR 根据检查点队列执行写出,在其他条件触发时,DBWR 由 Dirty List 执行写出。

请注意:此处的逻辑层和物理层是为了方便理解引用的。

在Buffer Cache中还存在Hash Bucket 和 Cache Buffer Chain两个结构用于快速从Oracle Buffer Cache中查找目标Buffer,其结构是这样的:

Oracle将Buffer Cache中的诸多Buffer利用hash算法分配到诸多bucket中,每个bucket存放Buffer的地址由Buffer数据块地址(DBA,Data Block Address)决定,bucket 和Buffer之间通过指针连接。在 Bucket 内部,存在Buffer Header这样一个结构,Buffer Header 存放的是对应数据块的概要信息(数据块的文件号、块地址、状态),通过 Cache Buffer Chain(双向链表)将所有的 Bucket 通过 Buffer Header 信息联系起来。在实际寻址Buffer Cache时,Oracle只需要查到hash bucket,通过其中的Cache Buffer Chain找到目标Buffer Header然后找到目标Bucket,最后通过hash bucket中的DBA找到Buffer Cache中的Buffer,读取Buffer Cache中的信息。

当众多SQL从Buffer Cache中读取数据时,会有那么一种情况:多条SQL共同访问一个或者多个Bucket来获取对应得内存数据,而Oracle力求串行操作故会对Buffer中Oracle访问目标chain产生Latch。为解决此问题,Oracle对于 Cache Buffer Chain 的只读访问,其 Latch 可以被共享(即共享Latch)。但是如果对目标块进行更改就需要产生独占Latch。这就是 Buffer Cache 与 Latch 竞争。由于 Buffer 根据 Buffer Header 进行散列,最终决定存入那一个 Hash Bucket,那么 Hash Bucket 的数量在一定程度上决定了每个 Bucket 中 Buffer 数量的多少,也就间接影响了产生Latch的多少。

 

2.Shared Pool :Shared Pool又是SGA中的另一个重要组件,主要为Oracle用户进程分配内存,可以说是一个比较核心的组件。

Oracle Shared Pool主要由 Library Cache 、Dictionary cache 和Result Cache Memory(11g and later)三块区域组成,为了增加对大共享池的支持,Shared Pool Latch 从原来的一个增加到现在的7个,如果用户的系统有4个或者4个以上的cpu,oracle可以把Shared Pool分割为subShared Pool进行管理,每个subpool都拥有独立的机构、LRU和Shared Pool Latch。

(1)Dictionary cache:主要存放存放数据字典信息,包括表、视图等对象的结构信息,用户以及对象权限信息,这部分信息相对稳定,在 Shared Pool 中通过字典缓存单独存放,字典缓存的内容是按行(Row)存储的(其他数据通常按 Buffer 存储),所以又被称为 Row Cache,其信息可以通过 V$ROWCACHE 查询。

(2)Library Cache:

oracle中所有的库缓存对象(Library Cache Object)都是以库缓存对象句柄(Library Cache Object Handle)的形式存放的,库缓存对象句柄是以哈希表(Hash Table)的方式存储在Library Cache中,在Hash Table中有多个Hash Bucket,Hash Bucket中存放了哈希值相同数库缓存对象句柄,不同对象句柄之间通过指针形成库缓存对象句柄链表(Library Cache Object Handles)。

在库缓存对象句柄链表(Library Cache Object Handle)中有很多的属性,其中NAME代表库缓存对象的名称(即SQL文本或表名);Namespace代表库缓存对象对应的分组名,不同类型的库缓存对象可能对应相同的分组名。

 

(3)Result Cache Memory(for 11g and later):

结果集缓存就是讲结果集存放在Shared Pool中,经过多次调用可以达到consistent gets=0,结果集缓存大大降低了特定SQL的执行成本。Oracle 通过一个新引入初始化参数 result_cache_max_size 来控制该 Cache 的大小。如果result_cache_max_size=0 则表示禁用该特性。参数 result_cache_max_result 则控制单个缓存结果可以占总的 Server Result Cache 大小的百分比

[oracle@edsir4p1-PROD1 ~]$ sqlplus / as sysdba

SQL*Plus: Release 11.2.0.1.0 Production on Sun Sep 1 03:31:59 2019

Copyright (c) 1982, 2009, Oracle.  All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SYS@PROD1> 
SYS@PROD1> 
SYS@PROD1> set linesize 200;
SYS@PROD1> set pagesize 10000;
SYS@PROD1> set autotrace trace;
SYS@PROD1> select * from hr.employees where salary>3000;

81 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 1445457117

-------------------------------------------------------------------------------
| Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |           |    81 |  5589 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMPLOYEES |    81 |  5589 |     3   (0)| 00:00:01 |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("SALARY">3000)


Statistics
----------------------------------------------------------
         33  recursive calls
          0  db block gets
         17  consistent gets
          0  physical reads
          0  redo size
       7532  bytes sent via SQL*Net to client
        475  bytes received via SQL*Net from client
          7  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         81  rows processed

SYS@PROD1> show parameter result
SYS@PROD1> set autotrace off;
SYS@PROD1> show parameter result

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
client_result_cache_lag              big integer 3000
client_result_cache_size             big integer 0
result_cache_max_result              integer     5
result_cache_max_size                big integer 3136K
result_cache_mode                    string      MANUAL
result_cache_remote_expiration       integer     0
--开启表HR.EMPLOYEES的结果集缓存模式为force。
SYS@PROD1> alter table hr.employees result_cache(mode force);

Table altered.

SYS@PROD1> alter system set result_cache_max_size=15m;

System altered.
发现此时consistent gets和physical reads已经全为0了,直接从result cache memory读取结果集
SYS@PROD1> set autotrace trace;
SYS@PROD1> select * from hr.employees where salary>3000;

81 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 1445457117

-------------------------------------------------------------------------------------------------
| Id  | Operation          | Name                       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                            |    81 |  5589 |     3   (0)| 00:00:01 |
|   1 |  RESULT CACHE      | 9cgfp0b36ugmzb9s3wkk1thpmm |       |       |            |          |
|*  2 |   TABLE ACCESS FULL| EMPLOYEES                  |    81 |  5589 |     3   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("SALARY">3000)

Result Cache Information (identified by operation id):
------------------------------------------------------

   1 - column-count=11; dependencies=(HR.EMPLOYEES); name="select * from hr.employees where salary>3000"


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
       7532  bytes sent via SQL*Net to client
        475  bytes received via SQL*Net from client
          7  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         81  rows processed

(4)redo and undo(for 10g and later):

在Oracle 10g以后为了解决redo undo 产生的Latch争用和IO问题,在Shared Pool中单独分配了一块专用内存,用于缓存相应的结果集,减少Latch和IO争用。

redo:在Log Buffer中申请了多块 Private Redolog Strands区域,同时绑定在Shared Pool中分配的多块区域,每个事物在Shared Pool中产生的redo信息由Shared Pool中的专用区域暂时存储,最后一起写入到 Log Buffer对应的 Private Redolog Strands中,通过lgwr统一写入 online redo。

undo:在 Shared Pool中单独分配了一块区域IMU Buffer,用于转储临时产生的undo块,最后有序批量的写入undo段中。

 

(5)Golabl Resource Directory(for RAC)

在Oracle RAC中还需要在Shared Pool中单独分配一块区域(GRD)用于存储实例间的通讯信息和锁信息,在GRD中包含 GCS(global Cache serves)和GES (global queue servers)两个服务。

GCS(global Cache serves):协调集群中数据块中全局锁的处理访问(cache fusion),处理工作的进程是LMS

LMS:维护数据文件状态和每个高速缓存块的记录。该LMS过程还控制到远程实例的消息流

 

GES (global queue servers):协调集群中的其他资源(锁),保证实例信息同步,维护各节点的数据字典表和库缓存一致性。处理工作的进程是LMD和LCK0

LMD:管理每个实例的传入资源请求

LCK0:管理 非cache fusion资源请求,例如库和行缓存请求、实例本身的锁

 

(6)Other:

除了以上列举的一些组件以外,Oracle Shared Pool中还有一些其他的组件,这些组件不像Library Cache 那样一直存在,例如rman备份恢复时也需要在Shared Pool中申请内存,这个内存在完成备份恢复之后就释放了。

 

 

PGA

在专用模式下,客户端的每一个process都由服务端专门的server process接管,并且在pga中分配相应的区域

固定 PGA(Fixed PGA):固定 PGA 和固定 SGA 类似,包含了大量原子变量、小的数据结构和指向可变 PGA 的指针,这些变量在源码中定义,在编译时分配,可以被认为是 PGA 的保留内存

可变 PGA(Variable PGA): 可变 PGA 通过具体的内存 Heap 分配来实现,其空间分配与使用时可以变化的,通过内部视图 X$KSMPP([K]ernel [S]ervice [M]emory [P]GAhea[P])可以查询可变 PGA 内存的分配和使用情况。PGA 的可变区中主要包含会话内存及私有 SQL 区等。

会话内存 - Session Memory:用于存放会话的登录信息以及其他相关信息,对于共享服务器模式,这部分内存是共享而非私有的。

私有的 SQL 区 - Private SQL Area:Private SQL Area 包含绑定变量信息、查询执行状态信息以及查询工作区等。每个发出 SQL 查询的会话都拥有一块私有 SQL 区,对于专用服务器模式,这部分内存在 PGA 中分配,对于共享服务器模式,这部分内存在 SGA 中分配。

永久区域 - Persistent Area:这个区域包含绑定变量等信息,这部分内存只有在游标被关闭时才会被释放

运行时区域 – Runtime Area:这个区域存放了 SQL 语句运行时所需要的信息,在执行请求时首先创建,其中包含了查询执行的状态信息(如对于全表扫描,则记录全表扫描的进度等)、SQL work areas(这部分区域在内存密集型请求下分配,如 Sort或者 Hash-Join 等,对于 DML 语句来说,SQL 语句执行完毕就释放该区域,对于查询语句则是在记录返回后或查询取消时释放)

UGA 中的内存分配可以通过内部表 X$KSMUP(X$KSMUP - [K]ernel [S]ervice [M]emory[U]GA Hea[P])查询得到。UGA 堆包含了存储一些固定表(X$表)的永久内存(依赖于特定参数的设置,如 OPEN_CursorS,OPEN_LINKS 和 MAX_ENABLED_ROLES)。除此以外,大部分的 UGA 用于私有 SQL 区和 PL/SQL 区。

讨论完sga和pga后讨论一下Shared Pool中的游标:

 

游标的存在直接影响oracle执行效率,在之前文章中讨论了优化器和统计信息对执行计划的影响,但是优化器估算出来的执行计划也不完全等价于实际中的运行成本,oracle中的一条SQL运行时会受Buffer Cache、Cursor、Latch等诸多因素影响。实际中相同的SQL在oracle中最理想的情况下就是使用一条执行计划,之后的语句直接使用此执行计划,那么此时的实际成本就低于CBO估算的成本。实际上,oracle中解析树和执行计划就是以Shared Cursor的形式存放在Library Cache中,在专用模式下,一个会话也会在pga中申请对应的内存,并在内存里产生Session Cursor以供sql调用,同一个会话中的相同SQL会使用同一个Session Cursor,此时就不需要再次打开可一次调用,这也会大大降低SQL的实际运行成本。可以说oracle中以游标标记了已经执行过的所有记录,并通过此方式降低非必须的性能消耗。

 

Shared Cursor

上面已经讨论了Shared Pool。当一条SQL执行是首先是与存放在Dictionary cache 中的数据字典、表和视图等信息进行校验解析,然后再在Library Cache中申请Latch在其中找到相应解析树和执行计划,通过此执行计划直接读取目标表中的数据块到Buffer Cache中进行数据关联返回结果;若在Library Cache中找不到相应的解析树和执行计划oracle就会立即释放 Library Cache Latch 同时在Shared Pool中申请 Shared Pool Latch申请内存,通过CBO在pga中利用目标表的统计信息估算出合理的执行计划并将执行计划存放到Library Cache中,然后释放 Shared Pool Latch在Library Cache中产生Library Cache Latch申请内存,读取目标表的数据块至Buffer Cache中(若Shared Pool中没有连续的trunk块就会报ORA - 04031错误)。一条SQL的执行过程大致是这样的,此处首先讨论Library Cache中的内存结构以及执行计划的存放方式和寻址方式。

 

Shared Cursor:存在于Library Cache中库缓存对象的一种,对应的库缓存对象句柄链表的Namespace值为CRSR。它会存储目标SQL的SQL文本,解析树,该SQL所涉及的对象定义,该SQL使用的绑定变量类型和长度和SQL的执行计划等信息。为了减少库缓存对象句柄链表长度oracle引入了Parent Cursor和Child Cursor的概念。在oracle中Parent Cursor和Child Cursor的结构相同,Child Cursor的name属性为null,实际执行计划和解析树存放于Child Cursor中,意味着oracle只能通过Parent Cursor才能找到Child Cursor并且同一个Parent Cursor下的SQL文本或表名是相同的。换句话说oracle硬解析就产生产生一个Parent Cursor和Child Cursor。Shared Cursor就是Library Cache诸多缓存对象中的一种。

select * from hr.employees where salary>3000;

EMPLOYEE_ID FIRST_NAME           LAST_NAME                 EMAIL                     PHONE_NUMBER         HIRE_DATE JOB_ID         SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
----------- -------------------- ------------------------- ------------------------- -------------------- --------- ---------- ---------- -------------- ---------- -------------
        200 Jennifer             Whalen                    JWHALEN                   515.123.4444         17-SEP-03 AD_ASST          4400                   101            10

SYS@PROD1> select sql_text,sql_id ,version_count from v$sqlarea where sql_text like 'select * from hr.employees where salary>%';

SQL_TEXT                                           SQL_ID        VERSION_COUNT
-------------------------------------------------- ------------- -------------
select * from hr.employees where salary>:"SYS_B_0" 1cd3n1marv8qa             1


SYS@PROD1> select     plan_hash_value,Child_number from v$sql where sql_id='1cd3n1marv8qa';

PLAN_HASH_VALUE Child_NUMBER
--------------- ------------
     1445457117            0

oracle中根据目标SQL的SQL文本的哈希值去相应的Hash Bucket中找匹配的Parent Cursor,由于是hash 运算所以此过程对大小写是极其敏感的。

select * from hr.EMPLOYEES where SALARY>3000;
select * from hr.employees where salary>3000;

SYS@PROD1> select sql_text,sql_id ,version_count from v$sqlarea where sql_text like 'select * from hr.%';

SQL_TEXT                                           SQL_ID        VERSION_COUNT
-------------------------------------------------- ------------- -------------
select * from hr.EMPLOYEES where SALARY>:"SYS_B_0" 86vf8hxvtccg9             1
select * from hr.employees where salary>:"SYS_B_0" 1cd3n1marv8qa             1

Session Cursor:

Session Cursor是当前Session解析和执行的SQL载体,结构同Shared Cursor一致都是c语言的一种复杂架构,以哈希表的形式缓存在server process pga中

①Session Cursor存在于server process pga中,不同Session之间不能共享。

②Session Cursor生命周期内至少经历一次open、parse、bind、execute、fetch和close中的一个或多个阶段,若Session_CACHED_CursorS参数设置大于0则用过的Session Cursor会存放在server process pga中。

 

经过以上对Shared Cursor和Session Cursor的讨论可以很容易理解硬解析、软解析、软软解析

硬解析:当一条SQL在Library Cache产生Latch 进行查找解析树和执行计划时,首先找到对应Parent Cursor,在通过Parent Cursor找到相对应的Child Cursor,同Child Cursor中找到对应的解析树和执行计划,Parent Cursor和Child Cursor一定是成对存在的,若找不到Parent Cursor 或者找不到Child Cursor 就需要立即释放Latch,在Shared Pool中分配内存在server process pga中进行执行解析,这就是硬解析。

软解析:对比硬解析,若可以找到对应的一对Parent Cursor和Child Cursor,那么Oracle就不需要再重新分配内存重新执行解析了,而是使用当前Child Cursor存放的执行计划解析数直接从Buffer Cache中读取目标数据块返回查询结果。这就是软解析。

软软解析:对比软解析,软解析在执行过程中会产生对应的Session Cursor来承载SQL,首次产生Session Cursor直至结束一定是执行完整的open、parse、bind、execute、fetch和close过程的,那么在这个过程中会有额外的性能消耗,若在同一个会话可以使用同一个Session Cursor作为多条相同SQL的载体,意味着Shared Cursor 和Session Cursor 就都能找到匹配记录,不需要再重新生成Session Cursor直接拿来使用现有的Session Cursor,只需执行parse、bind、execute、fetch,使用完毕直接标记为Soft Closed,不需要close。显然软软解析是最理想的。

 

当目标SQL执行时对应的Session Cursor状态为execute(该SQL正在执行)时,Oracle就需要先把该SQL对应的Child Cursor 给pin到库缓存中(首先持有与库缓存相关的Latch,在持有Library Cache pin这个enqueue来实现(是Oracle另一种锁,比Latch要重遵循先进先出,不具有原子性)),防止被age out出去。

 

Shared Cursor 和Session Cursor总结:

①一个Session首先去server process pga中找是否存在匹配的Session Cursor。

②若pga中找不到匹配的Session Cursor,就会在server process pga中新生成一个Session Cursor和一对Shared Cursor(若找到了Parent Cursor找不到匹配的Child Cursor,就会生成一个Child Cursor)--硬解析

③若在pga中找不到匹配的Session Cursor,在Library Cache中找到了对应的Shared Cursor,就会在server process pga中产生一个新的Session Cursor并和Shared Cursor匹配-- 软解析

④若在pga中找到了匹配的Session Cursor,那么此时就不需要在去Library Cache中寻找对应的Shared Cursor,仅通过Session Cursor就可完整整个SQL的解析过程--软软解析

 

相关参数:

open_Cursors:用于设定Session中能够以open状态并存的Session Cursor个数

SYS@PROD1> show parameter  open_Cursors

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
open_Cursors                         integer     300
查看具体已经存放pga中的Session 个数:
SYS@PROD1> col Cursor_TYPE for a40;
SYS@PROD1> col SQL_TEXT for a60;
SYS@PROD1> col USER_NAME for a10;
SYS@PROD1> col SID for 999;
SYS@PROD1> set linesize 200;
SYS@PROD1> select * from v$open_Cursor where sid=173;

SADDR     SID USER_NAME  ADDRESS  HASH_VALUE SQL_ID        SQL_TEXT                                                     LAST_SQL_ SQL_EXEC_ID Cursor_TYPE
-------- ---- ---------- -------- ---------- ------------- ------------------------------------------------------------ --------- ----------- ------------------------------
457C8190  173 SYS        45902498 2195068792 asvzxj61dc5vs select timestamp, flags from fixed_obj$ where obj#=:1                              Session Cursor CACHED
457C8190  173 SYS        45940270  576224028 g6sky1sj5hysw table_1_ff_210_0_0_0                                                               OPEN
457C8190  173 SYS        400B8F88  337957580 a6u3yjca29nqc declare   m_stmt  varchar2(512); begin    m_stmt:='delete fr                       DICTIONARY LOOKUP Cursor CACHED
457C8190  173 SYS        459C79B0 3819099649 3nkd3g3ju5ph1 select obj#,type#,ctime,mtime,stime, status, dataobj#, flags                       DICTIONARY LOOKUP Cursor CACHED
457C8190  173 SYS        3C72332C  907602229 2205n7sv1ju9p table_4_9_138b_0_0_0                                                               OPEN-RECURSIVE
457C8190  173 SYS        3C787420 3199009429 2b85h9azau0np  select * from v$open_Cursor where sid=:"SYS_B_0"                         16777221 Session Cursor CACHED
457C8190  173 SYS        400B87E0 1949913731 3972rvxu3knn3 delete from sdo_geor_ddl__table$$                                                  PL/SQL Cursor CACHED
457C8190  173 SYS        3E8FF2F4 3296299297 5dwybn727m291 select count(FA#) from SYS_FBA_TRACKEDTABLES where OBJ# = 73                       DICTIONARY LOOKUP Cursor CACHED
457C8190  173 SYS        45936B04 1950984145 5sxszjtu4m9yj table_1_ff_218_0_0_0                                                               OPEN
457C8190  173 SYS        45931BC4  864012087 96g93hntrzjtr select /*+ rule */ bucket_cnt, row_cnt, cache_cnt, null_cnt,                       DICTIONARY LOOKUP Cursor CACHED
457C8190  173 SYS        458BAF38 2907818604 g4um54aqp3kmc table_1_ff_220_0_0_0                                                               OPEN
457C8190  173 SYS        45937840 3349278045 4n6n6yr3u3vax table_1_ff_214_0_0_0                                                               OPEN

12 rows selected.
 
Session_cached_Cursors:设定单个Session能够以soft closed状态并存的Session Cursor的总数

SYS@PROD1> show parameter Session_cached_Cursors; 

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
Session_cached_Cursors               integer     50

Cursor_space_for_time:解决Child Cursor上与Library Cache相关的Latch征用个,Oracle 11gr1之后用mutex替代了Latch,已经不在使用,在Oracle10g还在广泛使用。

SYS@PROD1> show parameter Cursor_space_for_time

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
Cursor_space_for_time                boolean     FALSE

在Cursor_space_for_time=false(默认)时,一个Session Cursor反复的进行parse、bind、execute、fetch过程(不论是否需要反复open 和close,在这里对下面要讨论的Latch争用起不到决定性作用,这里为理解方便假定使用Session Cursor共享),这个Session Cursor对应的Child Cursor上的Library Cache pin在每次execute之后就会完全释放。当再次执行时还需要反复进行以上一系列操作,在高并发时显然这会产生Latch争用。Cursor_space_for_time=true就是在Session Cursor 每次执行完之后依然持有其对Child Cursor上的Library Cache pin,不释放,当再次运行该SQL时就不需要在进行 parse、bind等操作,直接使用Child Cursor就可以,在11gr1之前可以近乎完美的解决Shared Pool中的Latch争用,当然由于Latch一直持有不释放也会造成给Shared Pool空间造成一定的压力,就会出现我们常见的ORA - 04031 。

 

隐式游标:

隐式游标是Oracle数据库的一种Session Cursor,它的生命周期管理不需要人为干涉,全部是由SQL引擎或者PL/SQL引擎自动来完成

SQL%FOUND:一条SQL语句执行成功后受其影响的而改变的记录数是否大于等于1。

SQL%NOFOUND:一条SQL语句执行成功后受其影响而改变的记录数是否为0。

SQL%ISOPEN:隐式游标是否处于open状态。

SQL%ROWCOUNT:一条SQL语句执行成功后受其影响而改变的记录的数量。

 

显式游标:

显示游标是Oracle数据库的另外一种类型的Session Cursor,通常用PL/SQL代码来管理其生命周期(open、fetch和close)

CursorNAME%FOUND:指定显示游标(Cursorname)是否至少有一条记录被fetch了。当一个显式游标被open后,如过一次都没有被fetch,那么CursorNAME%FOUND值为NULL;当这个显示游标被fetch后,CursorNAME%FOUND值为TURE,直到全部fetch完毕。而全部fetch完毕后,再次还行一次fetch,Oracle不会报错,只是此时CursorNAME%FOUND值为FALSE。

CursorNAME%NOTFOUND:指定显式游标是否已经fatch完毕了,逻辑上与CursorNAME%FOUND相反。当一个显式游标被open后,如过一次都没有被fetch,那么CursorNAME%NOTFOUND值为NULL;当这个显示游标被fetch后,CursorNAME%NOTFOUND值为FALSE,直到全部fetch完毕。而全部fetch完毕后,再次还行一次fetch,Oracle不会报错,只是此时CursorNAME%NOTFOUND值为TRUE。

CursorNAME%ISOPEN:执行显式游标是否没open,用于标准的exception处理流程中,close那些由于发生了exception而导致有显式游标没有正常关闭的情形。

CursorNAME%ROWCOUNT:迄今为止一共fetch多少行记录。当一个显式游标被open后,如过一次都没有被fetch,那么CursorNAME%ROWCOUNT值为0;当这个显示游标被open后,如果被fetch后范围结果集为空,那么CursorNAME%ROWCOUNT值也是0,只有这个显式游标被fetch过一次且返回结果集不为空,那么CursorNAME%ROWCOUNT的值不为0,并且随着每一次fetch,CursorNAME%ROWCOUNT的值都会递增,他的值就表示指定的显示游标迄今为止一共fetch的记录数。

 

参考游标(Ref Cursor)

和显示游标一样,参考游标也通常用于PL/SQL代码中,由PL/SQL管理参考游标的生命周期。

同样包含四种属性,同显式游标。

CursorNAME%FOUND:

CursorNAME%NOTFOUND:

CursorNAME%ISOPEN:

CursorNAME%ROWCOUNT:

 

绑定变量(Bind variable)

之前讨论过,在OLTP业务中会出现大量的SQL文本相同,只有每次传入的具体值不同,这样的SQL在实际解析时需要根据每条SQL的hash_value和SQL文本查找对应的Child Cursor,每次都要分配内存解析执行计划,为了减少无用的SQL解析带来的时间消耗,为每次传入的具体值列引入了绑定变量,实际上理想状态下一条SQL文本只需解析一次,剩下的只需要用产生的执行计划就可以。这大量介绍了硬解析的次数。

绑定变量典型用法:

declare
  vc_name varchar2(10);
begin 
  execute immediate 'select ename from emp where empno = :1' into vc_name using 7369;
  dbms_output.put_line(vc_name);
end;
/

PL/SQL绑定变量的典型用法:

declare
  vc_sql_1 varchar2(4000);
  vc_sql_2 varchar2(4000);
  n_temp_1 number;
  n_temp_2 number;
begin 
  vc_sql_1 := 'insert into emp(empno,ename,job) values(:1,:2,:3)';
  execute immediate vc_sql_1 using 7370,'CAP1','DBA';
  n_temp_1 :=sql%rowcount;
  vc_sql_2 := 'insert into emp(empno,ename,job) values(:1,:1,:1)';
  execute immediate vc_sql_2 using 7371,'CAP2','DBA';
  n_temp_2 :=sql%rowcount;
  dbms_output.put_line(to_char(n_temp_1+n_temp_2));
  commit;
end;
/

JAVA中绑定变量的典型用法:

String dml = "update emp set sal = ? where emono = ?";
pstmt = connection.prepareStatement(dml);
pstmt.clearBatch();
for(int i = 0 ; i < UPDATE_COUNT; ++i)
{
 pstmt.setInt(1,generateEmpno(i));
 pstmt.setInt(2,generateSal(i));
 pstmt.addBatch();
}
pstmt.executeBatch();
connection.commit();

在使用绑定变量时绑定变量数量不宜过多,数量过多在变量匹配时会消耗很长的时间,这无异于是不能接受的。

对于Shared Cursor还在Shared Pool中使用绑定变量的SQL我们可以通过V$SQL_BIND_CAPTURE查看具体传入的变量值。V$SQL_BIND_CAPTURE中记录了硬解析的绑定变量值,对于软解析或软软解析默认是每15分钟捕获一次,并且对于insert 语句只捕获where以后的绑定变量值,对于values字句中的绑定变量值不去捕获。

对于Shared Cursor已经age out出Shared Pool的使用绑定变量的SQL我们可以通过DBA_HIST_SQLSTAT或DBA_HIST_SQLBIND来查看。

 

前面文章《浅谈Oracle优化器和统计信息》已经做了讨论,对于出现严重列倾斜的不建议在该列使用绑定变量。在Oracle10g之前不会自动收集直方图信息,那么CBO就会错误估算每一个值的成本,选错执行计划;Oracle10g以后会自动收集了直方图信息,由于列倾斜严重目标值的Selectivity也不一样,使用绑定变量之后就会出现比较糟糕的现象就是如果第一次执行硬解析的目标值在该列中的Selectivity比较大甚至很大,那么该SQL的Cardinality也是比较大的,在CBO的情况下这种SQLOracle很可能会选择全表扫描的形式,那么使用该绑定变量的后续SQL也都是使用这个执行计划,就会出现严重的性能问题。这个现象直到11g以后引入自适应游标后才得到缓解,但是相应的也会带来其他问题(硬解析变多,软解析寻址变慢,Shared Pool空间紧张)

绑定变量窥探(Bind PeeKing):

前面已经讨论到了绑定变量可以大大缩减硬解析次数,给Oracle性能起到了很不错的帮助,那么深入讨论下去就会发现,在生产中除了主键约束列其他的表列或多或少都会有一定的列倾斜情况,对于列倾斜严重的列我们可以选择收集直方图信息来帮助CBO选择正确的执行计划,但是这样做也就意味着此列开始跟绑定变量see goodbye了,对于列倾斜不严重的或者只是有细微列倾斜的目标列使用绑定变量也会有那么一个问题 - 就是Oracle根本不知道每次传入的是一个什么样的具体值,对于每次传入值之后Oracle会使用默认的Selectivity和Cardinality,这种的执行计划显然是不准确的。这严重情况下会造成很大的故障,这个故障对数据库一定是致命的。Oracle为解决此问题在9i以后引入了绑定变量窥探(Bind Peeking):Oracle在每次使用绑定变量进行硬解析时,都会使用绑定变量做PeeKing这个动作,来具体亏窥探变量中传入的值,根据传入的值计算实际的Cardinality。

优点:

绑定变量窥探的优点是显而易见的:这个PeeKing动作实际上是为变量值选择正确的Selectivity和Cardinality,避免使用默认的值,选出正确的执行计划。

缺点:

由于出现列倾斜问题,这个PeeKing之后虽然解决了大部分变量值选择正确的执行计划问题,同时也对不具代表性的变量值产生了错误的执行计划,他会带来一些列问题- 如果第一次硬解析时传入的变量在目标列具有代表性,那么这个过程我们维护人员很难感受的到其中的差别,如果这个SQL的 Shared Cursor 被从Shared Pool中age out了,再次使用该绑定变量传入的值是不具代表性的值,那么绑定变量窥探之后就会产生比较高的执行计划,在后续的变量传入也会使用此执行计划。这会严重影响数据库性能,造成比较可怕的性能故障。

在Oracle 10g以后,Oracle会自动收集直方图信息,Oracle会更清晰的直到数据分布情况,绑定变量窥探带来的副作用更加明显。

 

通过以下验证绑定变量窥探,该数据库开启绑定变量窥探且没有使用常规游标共享。

SYS@PROD1> set linesize 200;
SYS@PROD1> col name for a40;
SYS@PROD1> col value for a10;
SYS@PROD1> select nam.ksppinm NAME, val.KSPPSTVL VALUE from x$ksppi nam, x$ksppsv val
  2  where nam.indx = val.indx and nam.ksppinm ='_optim_peek_user_binds';

NAME                                     VALUE
---------------------------------------- ----------
_optim_peek_user_binds                   TRUE

SYS@PROD1>  show parameter Cursor_sharing

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
Cursor_sharing                       string      EXACT

SYS@PROD1> create table CAP_1 as select * from dba_objects;

Table created.

SYS@PROD1> create index ind_id on cap_1(object_id);

Index created.

SYS@PROD1>  exec dbms_stats.gather_table_stats(ownname=>'sys',tabname=>'cap_1',estimate_percent=>100,cascade=>true,method_opt=>'for all columns size 1',no_invalidate =>false);

PL/SQL procedure successfully completed.

先看两条SQL和执行计划,可以看到分别使用了索引范围扫描和索引快速全扫描
SYS@PROD1> select count(*) from cap_1 where object_id between 999 and 1000;

  COUNT(*)
----------
         2

SYS@PROD1> select count(*) from cap_1 where object_id between 999 and 60000;

  COUNT(*)
----------
     58180

SYS@PROD1> set linesize 200;
SYS@PROD1> col sql_text for a80;
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';

SQL_ID        VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
4k1x0ubh7kpvg             1 select count(*) from cap_1 where object_id between 999 and 1000
gq0d20hmcr1g9             1 select count(*) from cap_1 where object_id between 999 and 60000

SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: 4k1x0ubh7kpvg
old   1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new   1: select * from table(dbms_xplan.display_Cursor('4k1x0ubh7kpvg',null,'advanced'))

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  4k1x0ubh7kpvg, Child number 0
-------------------------------------
select count(*) from cap_1 where object_id between 999 and 1000

Plan hash value: 3666266488

----------------------------------------------------------------------------
| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE   |        |     1 |     5 |            |          |
|*  2 |   INDEX RANGE SCAN| IND_ID |     3 |    15 |     2   (0)| 00:00:01 |
----------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   2 - SEL$1 / CAP_1@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
      DB_VERSION('11.2.0.1')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_ID"))
      END_OUTLINE_DATA
  */

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OBJECT_ID">=999 AND "OBJECT_ID"<=1000)

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - (#keys=0) COUNT(*)[22]


44 rows selected.

SYS@PROD1>  select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: gq0d20hmcr1g9
old   1:  select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new   1:  select * from table(dbms_xplan.display_Cursor('gq0d20hmcr1g9',null,'advanced'))

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  gq0d20hmcr1g9, Child number 0
-------------------------------------
select count(*) from cap_1 where object_id between 999 and 60000

Plan hash value: 4170857810

--------------------------------------------------------------------------------
| Id  | Operation             | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |        |       |       |    46 (100)|          |
|   1 |  SORT AGGREGATE       |        |     1 |     5 |            |          |
|*  2 |   INDEX FAST FULL SCAN| IND_ID | 54617 |   266K|    46   (3)| 00:00:01 |
--------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   2 - SEL$1 / CAP_1@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
      DB_VERSION('11.2.0.1')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX_FFS(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_ID"))
      END_OUTLINE_DATA
  */

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(("OBJECT_ID"<=60000 AND "OBJECT_ID">=999))

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - (#keys=0) COUNT(*)[22]


44 rows selected.
使用游标之后进一步查看绑定变量窥探
SYS@PROD1> var x number;
SYS@PROD1> var y number;
SYS@PROD1> exec :x :=999;

PL/SQL procedure successfully completed.

SYS@PROD1> exec :y :=1000;

PL/SQL procedure successfully completed.

SYS@PROD1> select count(*) from cap_1 where object_id between :x and :y;

  COUNT(*)
----------
         2

SYS@PROD1> exec :y :=60000;

PL/SQL procedure successfully completed.

SYS@PROD1>  select count(*) from cap_1 where object_id between :x and :y;

  COUNT(*)
----------
     58180

可以看到Oracle在执行第一条SQL时发生硬解析,自动触发绑定变量窥探,获取实际的Selectivity扫描方式为INDEX RANGE SCAN,在之后的SQL中直接使用该执行计划不再进行绑定变量窥探。
SYS@PROD1> set linesize 200;
SYS@PROD1> col sql_text for a80;
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';

SQL_ID        VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
a8nddr1sh1qds             1 select count(*) from cap_1 where object_id between :x and :y

SYS@PROD1> set pagesize 1000;
SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: a8nddr1sh1qds
old   1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new   1: select * from table(dbms_xplan.display_Cursor('a8nddr1sh1qds',null,'advanced'))

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  a8nddr1sh1qds, Child number 0
-------------------------------------
select count(*) from cap_1 where object_id between :x and :y

Plan hash value: 916475704

-----------------------------------------------------------------------------
| Id  | Operation          | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |        |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |        |     1 |     5 |            |          |
|*  2 |   FILTER           |        |       |       |            |          |
|*  3 |    INDEX RANGE SCAN| IND_ID |     3 |    15 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   3 - SEL$1 / CAP_1@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
      DB_VERSION('11.2.0.1')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_ID"))
      END_OUTLINE_DATA
  */

Peeked Binds (identified by position):
--------------------------------------

   1 - :X (NUMBER): 999
   2 - :Y (NUMBER): 1000

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(:X<=:Y)
   3 - access("OBJECT_ID">=:X AND "OBJECT_ID"<=:Y)

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - (#keys=0) COUNT(*)[22]


52 rows selected.

SYS@PROD1>  alter  system flush shared_pool; --这里模拟执行计划被age out 出Shared Pool,生产环境谨慎操作!!!

System altered.

SYS@PROD1> var x number;
SYS@PROD1>  var y number;
SYS@PROD1> exec :x :=999;

PL/SQL procedure successfully completed.

SYS@PROD1>  exec :y :=60000;

PL/SQL procedure successfully completed.

SYS@PROD1> select count(*) from cap_1 where object_id between :x and :y;

  COUNT(*)
----------
     58180

SYS@PROD1> exec :y :=1000;

PL/SQL procedure successfully completed.

SYS@PROD1> select count(*) from cap_1 where object_id between :x and :y;

  COUNT(*)
----------
         2

可以发现,模拟执行计划被age out出Shared Pool之后,先执行:y=60000的SQL之后,再次触发绑定变量窥探,使用实际的Selectivity。
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';

SQL_ID        VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
a8nddr1sh1qds             1 select count(*) from cap_1 where object_id between :x and :y

SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: a8nddr1sh1qds
old   1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new   1: select * from table(dbms_xplan.display_Cursor('a8nddr1sh1qds',null,'advanced'))

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  a8nddr1sh1qds, Child number 0
-------------------------------------
select count(*) from cap_1 where object_id between :x and :y

Plan hash value: 2157405733

---------------------------------------------------------------------------------
| Id  | Operation              | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |        |       |       |    46 (100)|          |
|   1 |  SORT AGGREGATE        |        |     1 |     5 |            |          |
|*  2 |   FILTER               |        |       |       |            |          |
|*  3 |    INDEX FAST FULL SCAN| IND_ID | 54617 |   266K|    46   (3)| 00:00:01 |
---------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   3 - SEL$1 / CAP_1@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
      DB_VERSION('11.2.0.1')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX_FFS(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_ID"))
      END_OUTLINE_DATA
  */

Peeked Binds (identified by position):
--------------------------------------

   1 - :X (NUMBER): 999
   2 - :Y (NUMBER): 60000

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter(:X<=:Y)
   3 - filter(("OBJECT_ID"<=:Y AND "OBJECT_ID">=:X))

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - (#keys=0) COUNT(*)[22]


52 rows selected.

SYS@PROD1> 

绑定变量分级(Bind Graduation):

Oracle会对PL/SQL中的文本类型的绑定变量定义的长度做了分级,不同长度的绑定变量在Library Cache分配的内存是不一样的。如果绑定变量长度发生改变,SQL文本长度也就发生了改变,由于Child Cursor存放的解析树和执行计划的同时还存放了SQL文本,这就意味着一旦绑定变量长度发生改变,那么其对应的解析树和执行计划也不能被使用,就会重新解析该语句。

等级:

第一等级:定义长度在32 byte以内的文本型绑定变量,对应的该等级绑定变量会分配32字节的内存空间。

第二等级:定义长度在33 - 128 byte的文本型绑定变量,对应的该等级绑定变量会分配128字节的内存空间。

第三等级:定义长度在129 - 2000 byte的文本型绑定变量,对应的该等级绑定变量会分配2000字节的内存空间。

第四等级:定义长度在2000 byte以上的文本型绑定变量,对应的该等级绑定变量会根据传入的实际绑定变量的值分配内存空间。

 

游标共享(Cursor sharing):

在产品开发阶段,开发可能并不会注意到SQL语句带来的重大影响,就会对一些SQL不适用绑定变量,在产品上线以后就会对数据库造成很大的压力

常规游标共享:

如上所述,面临这样的问题如果更改代码那是相当耗时的,在Oracle 8i中引入了常规游标共享(:"SYS_B_n"(n=0,1,2,3......)),在不改变应用代码的情况下,开启常规游标共享就会使用游标替换where子句中的具体值。

例如一条SQL“select * from emo where empno=7369”在开启了常规游标共享之后,Oracle就会替换改写成“select * from emo where empno=:"SYS_B_0"”

 

EXACT:Cursor_sharding的默认值,Oracle不会使用系统产生的绑定变量来替换目标SQL的文本中where条件或values子句中的具体值。

SIMILAR:Oracle会使用系统产生的绑定变量替换目标SQL的文本中where条件或values子句中的具体值。在Oracle 11gR1(引入自适应游标)之前这里的绑定变量替换仅适用于安全的谓词条件(where字句中等式连接的目标值条件)对于不安全的谓词条件(where字句使用了范围查询的条件)不会进行替换,即使SQL文本中显示了系统绑定变量其对应的解析树和执行计划也不能共享。

FORCE:Oracle会无条件的使用系统产生的绑定变量替换目标SQL的文本中的where条件或values子句中的具体值,不在区分安全的谓词条件和不安全的谓词条件。这种行为在11g以后引入自适应游标后不在被使用。

SYS@PROD1> show parameter  Cursor_sharing

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
Cursor_sharing                       string      EXACT

通过以下验证可以发现,开启常规游标共享之后,所有常量在Child Cursor均已系统游标的形式存放
SYS@PROD1> alter system set Cursor_sharing=similar;

System altered.

SYS@PROD1> show parameter Cursor_sharing;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
Cursor_sharing                       string      SIMILAR
SYS@PROD1> select count(*) from cap_1 where object_id between 1 and 3;

  COUNT(*)
----------
         2

SYS@PROD1>  select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';

SQL_ID        VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
a8nddr1sh1qds             1 select count(*) from cap_1 where object_id between :x and :y
5h2x4h75ztrau             1 select count(*) from cap_1 where object_id between :"SYS_B_0" and :"SYS_B_1"

SYS@PROD1> select count(*) from cap_1 where object_id between 1 and 2;

  COUNT(*)
----------
         1
由于在常规游标中对于where字句中的范围条件均被视为不安全谓词条件,所以看到的即使使用的游标依然是硬解析,执行两次之后产生了两个Child Cursor。这种问题在11gR2引入自适应游标之后得到一定的缓解,在下面我们会继续讨论自适应游标
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';

SQL_ID        VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
a8nddr1sh1qds             1 select count(*) from cap_1 where object_id between :x and :y
5h2x4h75ztrau             2 select count(*) from cap_1 where object_id between :"SYS_B_0" and :"SYS_B_1"

SYS@PROD1> select plan_hash_value,sql_id from v$sql where sQL_id='5h2x4h75ztrau';

PLAN_HASH_VALUE SQL_ID
--------------- -------------
      916475704 5h2x4h75ztrau
      916475704 5h2x4h75ztrau

而对于等式条件这种安全谓词条件,是完全可以使用常规游标共享的,执行多次之后仍然共享一条执行计划。
SYS@PROD1> select count(*) from cap_1 where object_id=1;

  COUNT(*)
----------
         0

SYS@PROD1> select count(*) from cap_1 where object_id=3;

  COUNT(*)
----------
         1

SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';

SQL_ID        VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
a8nddr1sh1qds             1 select count(*) from cap_1 where object_id between :x and :y
5h2x4h75ztrau             2 select count(*) from cap_1 where object_id between :"SYS_B_0" and :"SYS_B_1"
0s45fj0v5ba2p             1 select count(*) from cap_1 where object_id=:"SYS_B_0"

自适应游标共享(Adaptive Cursor Sharing):

诚如上述所述,使用绑定变量窥探之后会带来一些列的副作用,尤其是Oracle 10g以后这个副作用更加明显。为了解决绑定变量窥探带来的副作用,Oracle11g引入了自适应游标共享。

自适应游标就是在使用绑定变量窥探的前提下,能够使目标SQL在多个执行计划之间自适应的选择出合适的执行计划。其工作方式是在第一次通过硬解析产生一个执行计划,当在之后的SQL运行时会自动记录每一条SQL的runtime信息,并与上一条SQLruntime信息作比对,如果有较大的偏差,那么就会重新硬解析产生一个新的执行计划。

自适应游标共享第一步就是扩展游标共享(Extended Cursor sharing),将目标SQL对应的Child Cursor标记为Bind Sensitive(Oracle认为含某个变量的目标SQL的执行计划可能会随着变量值变化而变化)

条件:①启动了绑定变量窥探

            ②该SQL使用了绑定变量(系统绑定变量和SQL自定义绑定变量)

            ③该SQL使用的是不安全的谓词条件(范围查询,有直方图信息列的等值查询)

自适应游标共享第二步是将目标SQL对应的Child Cursor标记为Bind Aware(Oracle已经确定含某个变量的目标SQL的执行计划会随着变量值变化而变化)

条件:①该SQL所对应的Child Cursor已经在之前标记为Bind Sensitive。

            ②该SQL在接下来两次执行时所对应的的runtime和之前硬解析产生的runtime有较大偏差。

自适应游标的整体执行流程大致应该是这样的:

①当目标SQL第一次执行,Oracle会硬解析根据目标表的一些列信息(如统计信息,直方图信息,还有参数信息等)来判断是否想目标SQL对应的Child Cursor标记为 Bind Sensitive,对于标记为 Bind Sensitive的Child Cursor,Oracle会吧执行该SQL所对应的的runtime统计信息额外的存储到Child Cursor中。

②目标SQL第二次执行时,Oracle会使用软解析,重用第一次硬解析的解析树和执行计划。

③当目标SQL第三次执行时,若该SQL所对应的的Child Cursor已经被标记为Bind Sensitive,同时第二次和第三次执行该SQL时所记录的runtime统计信息和该SQL第一次实行时所记录的runtime统计信息由较大差异,则该SQL第三次被执行时就会使用硬解析,此时Oracle就会产生一个新的执行计划,对应的Child Cursor应当在原来Parent Cursor下,并且这个新的Child Cursor标记为Bind Aware

④对于标记为Bind Aware的Child Cursor所对应的目标SQL,当该SQL再被执行时,Oracle就会根据当前传入的绑定变量值所对应的谓词选择率来决定使用硬解析还是软解析/软软解析。遵循的原则:当前传入的绑定变量值所在的谓词条件的可选择率处于该说完了之前硬解析时同名谓词条件在V$SQL_CS_STATISTICS中记录的可选择率范围之内,使用软解析/软软解析,并重用之前Child Cursor中存储的解析树和执行计划,反之是硬解析。

 

首先确定当前开启自适应游标并且常规游标为默认值,经过一下验证自适应游标

SYS@PROD1>  select nam.ksppinm NAME, val.KSPPSTVL VALUE from x$ksppi nam, x$ksppsv val
  2    where nam.indx = val.indx and nam.ksppinm like '_optimizer_%_Cursor_sharing%';

NAME                                     VALUE
---------------------------------------- ----------
_optimizer_extended_Cursor_sharing       UDO
_optimizer_extended_Cursor_sharing_rel   SIMPLE
_optimizer_adaptive_Cursor_sharing       TRUE

SYS@PROD1> alter system set Cursor_sharing=exact;

System altered.

SYS@PROD1> show parameter Cursor_sharing;

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
Cursor_sharing                       string      EXACT

SYS@PROD1> create index ind_type on cap_1(object_type);

Index created.
手工更改一些数据,产生较大的列倾斜,方便验证数据
SYS@PROD1> update cap_1 set object_type='TABLE' where rownum <60002;

60001 rows updated.

SYS@PROD1> update cap_1 set object_type='CLUSTER' where rownum <100;

99 rows updated.

SYS@PROD1> commit;

Commit complete.

SYS@PROD1> exec dbms_stats.gather_table_stats(ownname=>'sys',tabname=>'cap_1',estimate_percent=>100,cascade=>true,method_opt=>'for all columns size auto',no_invalidate =>false);

PL/SQL procedure successfully completed.
收集完直方图之后查看直方图信息
SYS@PROD1>  select column_name,num_buckets,histogram from dba_tab_col_statistics where table_name='CAP_1' and column_name='OBJECT_TYPE';

COLUMN_NAME                    NUM_BUCKETS HISTOGRAM
------------------------------ ----------- ---------------
OBJECT_TYPE                             30 FREQUENCY

SYS@PROD1> alter system  flush shared_pool; --生产环境谨慎操作!!! 消除其他的影响因素

System altered.

SYS@PROD1> var x varchar2(30);
SYS@PROD1> exec :x :='CLUSTER';

PL/SQL procedure successfully completed.

SYS@PROD1> select count(*) from cap_1 where object_type=:x;

  COUNT(*)
----------
        99
第一次执行语句,硬解析产生执行计划,Child Cursor标记为Bind Sensitive,并在Child Cursor中记录runtime
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';

SQL_ID        VERSION_COUNT SQL_TEXT                                                                          EXECUTIONS
------------- ------------- --------------------------------------------------------------------------------  ----------
d76x20r4px2bk             1 select count(*) from cap_1 where object_type=:x                                            1

SYS@PROD1> col is_bind_sensitive for a20;
SYS@PROD1> col is_bind_aware for a20;
SYS@PROD1> col is_shareable for a20;
SYS@PROD1> select Child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware,is_shareable,plan_hash_value from v$sql where sql_id='d76x20r4px2bk';

Child_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE    IS_BIND_AWARE        IS_SHAREABLE         PLAN_HASH_VALUE
------------ ---------- ----------- -------------------- -------------------- -------------------- ---------------
           0          1          50 Y                    N                    Y                         3375217814
此时的执行计划是通过绑定变量窥探产生的贴近实际的执行计划
SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: d76x20r4px2bk
old   1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new   1: select * from table(dbms_xplan.display_Cursor('d76x20r4px2bk',null,'advanced'))

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  d76x20r4px2bk, Child number 0
-------------------------------------
select count(*) from cap_1 where object_type=:x

Plan hash value: 3375217814

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |       |       |     3 (100)|          |
|   1 |  SORT AGGREGATE   |          |     1 |     7 |            |          |
|*  2 |   INDEX RANGE SCAN| IND_TYPE |    99 |   693 |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   2 - SEL$1 / CAP_1@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
      DB_VERSION('11.2.0.1')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_TYPE"))
      END_OUTLINE_DATA
  */

Peeked Binds (identified by position):
--------------------------------------

   1 - :X (VARCHAR2(30), CSID=873): 'CLUSTER'

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OBJECT_TYPE"=:X)

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - (#keys=0) COUNT(*)[22]


49 rows selected.
变量传入其他值继续第二遍执行
SYS@PROD1> exec :x :='TABLE';

PL/SQL procedure successfully completed.

SYS@PROD1> select count(*) from cap_1 where object_type=:x;

  COUNT(*)
----------
     61313
通过以下验证可以发现第二遍执行Oracle会使用游标共享方式进行软解析,但是由于绑定变量传入值和之前第一次传入值的结果集存在较大的差距,此时可以看到BUFFER_GETS从50猛增到304,这是一个很正常的现象,因为此时所选用的执行计划已经不是合适的执行计划了,此时Child Cursor仍然被标记为Bind Sensitive,并记录runtime到Child Cursor。
SYS@PROD1> select sql_id,version_count,sql_text,executions from v$sqlarea where sql_text like 'select count(*) from cap_1%';

SQL_ID        VERSION_COUNT SQL_TEXT                                                                         EXECUTIONS
------------- ------------- -------------------------------------------------------------------------------- ----------
d76x20r4px2bk             1 select count(*) from cap_1 where object_type=:x                                           2

SYS@PROD1>  select Child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware,is_shareable,plan_hash_value from v$sql where sql_id='d76x20r4px2bk';

Child_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE    IS_BIND_AWARE        IS_SHAREABLE         PLAN_HASH_VALUE
------------ ---------- ----------- -------------------- -------------------- -------------------- ---------------
           0          2         304 Y                    N                    Y                         3375217814

SYS@PROD1> select Child_number,predicate,range_id,low,high from v$sql_cs_Selectivity where sql_id='d76x20r4px2bk';

no rows selected
第三次执行该语句,发现此时Oracle自适应游标已经发生了改变,由于之前已经将Child Cursor标记为Bind sensitive了并且连续两次runtime出现了比较大的差距,触发Oracle自适应游标重新进行硬解析再次触发绑定变量窥探,产生了一个贴近实际的执行计划,此时Child Cursor标记为Bind Aware。
SYS@PROD1> select count(*) from cap_1 where object_type=:x;

  COUNT(*)
----------
     61313

SYS@PROD1> select sql_id,version_count,sql_text,executions from v$sqlarea where sql_text like 'select count(*) from cap_1%';

SQL_ID        VERSION_COUNT SQL_TEXT                                                                         EXECUTIONS
------------- ------------- -------------------------------------------------------------------------------- ----------
d76x20r4px2bk             2 select count(*) from cap_1 where object_type=:x                                           3

SYS@PROD1>  select Child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware,is_shareable,plan_hash_value from v$sql where sql_id='d76x20r4px2bk';

Child_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE    IS_BIND_AWARE        IS_SHAREABLE         PLAN_HASH_VALUE
------------ ---------- ----------- -------------------- -------------------- -------------------- ---------------
           0          2         304 Y                    N                    Y                         3375217814
           1          1         455 Y                    Y                    Y                          549666362
发现此时该sql_id下面已经出现了两个Child Cursor,硬解析新生成的执行计划使用INDEX FAST FULL SCAN扫描目标表
SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: d76x20r4px2bk
old   1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new   1: select * from table(dbms_xplan.display_Cursor('d76x20r4px2bk',null,'advanced'))

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  d76x20r4px2bk, Child number 0
-------------------------------------
select count(*) from cap_1 where object_type=:x

Plan hash value: 3375217814

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |       |       |     3 (100)|          |
|   1 |  SORT AGGREGATE   |          |     1 |     7 |            |          |
|*  2 |   INDEX RANGE SCAN| IND_TYPE |    99 |   693 |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   2 - SEL$1 / CAP_1@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
      DB_VERSION('11.2.0.1')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_TYPE"))
      END_OUTLINE_DATA
  */

Peeked Binds (identified by position):
--------------------------------------

   1 - :X (VARCHAR2(30), CSID=873): 'CLUSTER'

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OBJECT_TYPE"=:X)

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - (#keys=0) COUNT(*)[22]

SQL_ID  d76x20r4px2bk, Child number 1
-------------------------------------
select count(*) from cap_1 where object_type=:x

Plan hash value: 549666362

----------------------------------------------------------------------------------
| Id  | Operation             | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |          |       |       |    81 (100)|          |
|   1 |  SORT AGGREGATE       |          |     1 |     7 |            |          |
|*  2 |   INDEX FAST FULL SCAN| IND_TYPE | 61313 |   419K|    81   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   2 - SEL$1 / CAP_1@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
      DB_VERSION('11.2.0.1')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX_FFS(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_TYPE"))
      END_OUTLINE_DATA
  */

Peeked Binds (identified by position):
--------------------------------------

   1 - :X (VARCHAR2(30), CSID=873): 'TABLE'

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("OBJECT_TYPE"=:X)

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - (#keys=0) COUNT(*)[22]


98 rows selected.
此时已经可以看到新生成的Child Cursor标记为Bind Aware了
SYS@PROD1>  select Child_number,predicate,range_id,low,high from v$sql_cs_Selectivity where sql_id='d76x20r4px2bk';

Child_NUMBER PREDICATE                                  RANGE_ID LOW        HIGH
------------ ---------------------------------------- ---------- ---------- ----------
           1 =X                                                0 0.761484   0.930703

缺陷:

①增加了额外的硬解析

②可能会增加Parent Cursor下的Child Cursor数量,会增加软解析和软软解析查找匹配Child Cursor的工作量

③额外的Child会增加Shared Pool在存储空间方面的压力

④自适应游标每次改变执行计划都需要比对连续两次的runtime,故自适应游标只能在一定程度上弥补了绑定变量窥探带来的副作用,并不能完美的消除此隐患。

 

禁用自适应游标:

①将隐含参数_OPTIMIZER_EXTENDED_CURSOR_SHARING和_OPTIMIZER_EXTENDED_CURSOR_SHARING_REL设置为NONE,这样就关闭了可扩展游标共享,不会将目标SQL对应的Child Cursor标记为Bind Sensitive。

②将隐含参数_OPTIMIZER_ADAPTIVE_CURSOR_SHARING设置为FALSE,所有的Child Cursor都不能标记为Bind Aware。

 

总结:

Oracle一条语句执行过程

①客户端通过监听连接数据库首先产生一个user process 连接服务端的server process,在Oracle产生server process时会同时在pga中分配进程内存,这就是 server process pga,之后该会话连接的所有排序和解析等操作都是通过此pga区域完成。user process 连接server process以后会与instance 建立connect。

②当Oracle客户端发出一条SQL,首先通过监听将指令传入到instance中,在shared pool中的dictionary cache中进行对语句的语法 数据字典,目标表视图和表列进行校验。语法语义校验之后会在library cache中进一步执行。

③首先在server process pga中查找session cursor,如果有可用的session cursor则直接用此游标共享执行计划--软软解析,但是首次连接会话,pga中没有session Process 这个过程遍历完之后直接进行下一步。

④在library cache中产生library cache latch 在library cache中bucket中遍历parent cursor,并在parent cursor根据SQL文本查找对应的child cursor,若能找到对应的child curosr,那么就不需要在分配内存解析执行计划了--软解析,若没有找到对应的child curosr,就会进行下一步分配内存解析执行计划。

⑤释放library cache latch 在shared pool中生成一个shared pool latch ,在pga中通过CBO进行查询转换成等价的简单SQL,之后再解析生成执行计划,并将产生的执行计划存放到 library cache中对应的child curosr(新产生一对 shared cursor),然后释放shared pool latch在library cache 再次产生library cache latch,遍历执行child curosr(执行④步骤)--硬解析。

⑥一旦找到了解析树和执行计划下一步就是从buffer cache找到目标表数据块是否有缓存,并读取到buffer cache中。首先在Buffer cache中的Hash Bucket中查找对应数据块对应的bucket header,若找到对应的bucket则直接操作buffer cache中的缓存快,若没有则通过LRU机制释放部分buffer 和LRU List 对应的bucket,然后将block读取到buffer cache中,读取buffer cache中目标数据块。

若需要更改就需要做以下操作

1>检查该目标数据块是否有latch,若存在则需要等待或者通知持有者释放latch。

2>在shared pool中对应事物申请一个内存区域,并且在Log Buffer中申请一个subLog Buffer ,用于将更改记录写入log buffer。

3>在shared pool中对应事物申请一个内存区域,读取undo segment 到shared pool并映射到buffercache中,用于存放更改数据的before image。

⑦若是在RAC环境中在查找Buffer Cache中的数据块前首先要报告master node,master node通知持有该目标块的节点释放锁或者对锁降级,然后通知当前节点读取目标数据块进行⑥步骤。

⑧在更改完之后用户commit提交之后,需要做一下操作:

1>sublog buffer中的数据写入到online log.

2>在buffer cache中的更改数据行标记为已更改未写入数据块。

3>undo segment中的状态标记为inactive,表示事物已提交。

4>在达到触发条件之后触发增量检查点将 checkpind scn写入数据块,此时并没有将数据块数据完全完成更改,在buffer cache中的LRU移除之前将所有更改过的数据写入数据块,完成整个操作。

在实际生产环境中,这个过程受不同环境,不同数据库版本,以及不同的参数配置影响会有不同,在commit之后也会受实际生产环境的buffer cache大小 io性能,实际执行会有不同。

 

                                                                                                                                                                             --- End

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章