草稿 - 游标,硬解析,软解析 等
六, Library Cache中GET, PIN, LOCK概念2.父游标和子游标每种类型的dml语句都需要如下阶段:Create a Cursor 创建游标Parse the Stat...
六, Library Cache中GET, PIN, LOCK概念
2.父游标和子游标
每种类型的dml语句都需要如下阶段:
Create a Cursor 创建游标
Parse the Statement 分析语句
Bind Any Variables 绑定变量
Run the Statement 运行语句
Close the Cursor 关闭游标
当数据库第一次对一条SQL语句进行硬解析的时候,会在库缓存中分配一些内存,并将新产生的父游标保存进去。与父游标有关的关键信息是这个SQL语句的文本,这个时候,会在v$sqlarea里面插入一条记录。那么,在什么情
况下会产生子游标呢,当数据库又碰到一条完全相同SQL语句,但是语句的执行计划和执行环境发生了变化,比如由于绑定变量窥测而产生的不一致的执行计划,由于SQL的初始化参数optimizer_mode的不同以及绑定变量分级
的情况都会产生子游标,当产生子游标的时候,会在v$sql里面插入一条记录。并且v$sqlarea里的VERSION_COUNT字段的值会加1。
eg:
--多个SQL语句只有在它们的文本完全一致的情况下才能共享一个父游标,这是最基本的要求。
--窗口1执行
sys/SYS>select * from t;
no rows selected
sys/SYS>select * from t;
no rows selected
sys/SYS>SELECT * from t;
no rows selected
--窗口2执行
sys/SYS>select hash_value,sql_text,executions from v$sqlarea where sql_text like '%from t';
HASH_VALUE SQL_TEXT EXECUTIONS
---------- ---------------------------------------- ----------
3542409071 SELECT * from t 1
520543201 select * from t 2
可以看到由于SQL文本不同产生了两个父游标。
sys/SYS>select HASH_VALUE,child_number,sql_text from v$sql where sql_text like '%from t';
HASH_VALUE CHILD_NUMBER SQL_TEXT
---------- ------------ ----------------------------------------
520543201 0 select * from t
3542409071 0 SELECT * from t
在v$sql里面也插入了两条子游标的记录,但是CHILD_NUMBER都是0,其实对应的就是父游标。
现在再来看下由于optimizer_mode的不同而产生子游标的情况。
--窗口1执行
sys/SYS>alter session set optimizer_mode=all_rows;
Session altered.
sys/SYS>select * from tt;
no rows selected
sys/SYS>alter session set optimizer_mode=first_rows_10;
Session altered.
sys/SYS>select * from tt;
no rows selected
--窗口2执行
sys/SYS>select hash_value,sql_text,executions,VERSION_COUNT from v$sqlarea where sql_text like '%from tt';
HASH_VALUE SQL_TEXT EXECUTIONS VERSION_COUNT
---------- ---------------------------------------- ---------- -------------
3762890390 select * from tt 2 2
sys/SYS>select HASH_VALUE,child_number,sql_text from v$sql where sql_text like '%from tt';
HASH_VALUE CHILD_NUMBER SQL_TEXT
---------- ------------ ----------------------------------------
3762890390 0 select * from tt
3762890390 1 select * from tt
可以看到,SQL文本是完全相同的,所以两个子游标共享了一个父游标。但是由于optimizer_mode的不同,所以生成了2个子游标。
如果产生了子游标,那么说明肯定产生了某种mismatch,那么如何来查看是何种原因产生了mismatch呢?这就要通过v$sql_shared_cursor了。
sys/SYS>select kglhdpar, address,auth_check_mismatch, translation_mismatch,OPTIMIZER_MISMATCH
2 from v$sql_shared_cursor
3 where kglhdpar in
4 ( select address
5 from v$sql
6 where sql_text like '%from tt');
KGLHDPAR ADDRESS A T O
-------- -------- - - -
89BB8948 89BB83CC N N N
89BB8948 89BB5E78 N N Y
可以看到OPTIMIZER_MISMATCH列第二行的值为Y,这说明了正是由于optimizer_mode的不同而产生了子游标。
最后,父游标和子游标的意义何在?其实一切都是为了共享。以减少再次解析的资源浪费。
这里如果是不同用户或者期间对象权限更改,就算是SQL文本一样,也是无法共享子游标的,
生成handle,其实就是要想共享池申请空闲的内存,期间获得shared pool latch ,那么很容易产生冲突。handle其实就是存放的父游标,真正的执行计划是存放在子游标上的,也就是heap6上,一个父游标可能对应多个子游
标,比如,不同用户下的相同SQL,就会造成1个父对多个子游标,这种父游标存在,而要重新生成子游标,就是relaod,需要耗硬件资源,数据库性能也就十分低下,所以我们要避免硬解析和reload,解析过程中很消耗资源
,而且容易造成 latch的冲突,父游标里主要包含SQL文本,父游标打开时,是被锁住的,也就是不能交换出library cache ,
只要是对表做DDL操作都会重新解析原来的DML SQL .
http://space.itpub.net/22034023/viewspace-664511
无法共享父游标
没有共享的原因就是因为大小写或者空格数据不一样,但是这里最大的原因是没有使用绑定变量。
可以共享父游标,但不可共享子游标:
子游标中最关键的信息就是执行计划和执行环境,执行环境
很重要,如果执行环境改变了,则执行计划也会相应的有变动;
只有共享了父游标和执行环境一样了,那么才可能共享子游标。
例如我们alter session set optimizer_mode=choose/first_rows_n/rule/first_rows/all_rows,这样都会产生相应的子游标,
因为执行环境变了;
可以通过如下语句发现变化:
select sql_id,child_number,sql_text,executions,plan_hash_value,optimizer_mode from v$sql where lower(sql_text) like '@#$';
select sql_id,child_number,sql_text,optimizer_mode,plan_hash_value from v$sql where sql_id=(select prev_sql_id from v$session
where sid=sys_context('userenv','sid'));
有些子游标,紧紧是因为环境改变了,而执行计划是相同的,这个我们可以通过plan_hash_value看出;
而通过查询v$sql_shared_cursor可以看出是什么原因导致了子游标不能被共享:
select child_number,optimizer_mode,optimizer_mode_mismatch from v$sql_shared_cursor
where sql_id='kdfj';
而硬解析通常是由于 不能共享父游标造成的,而非子游标。
Shared pool深入分析及性能调整(二)
2007年11月26日 11:58
2.2 转储library cache
oracle提供了命令可以对library cache中的内容进行转储。于是我们可以对library cache进行转储,从而对上面所说的library cache的内容进行验证。
ALTER SESSION SET EVENTS 'immediate trace name library_cache level N';
这里的N可以取的值分别为:
1 转储library cache的统计信息
2 转储hash表的汇总信息
4 转储library cache object的基本信息
8 转储library cache object的详细信息
16 转储heap size的信息
32 转储heap的详细信息
在测试之前,我们先创建一个测试表,然后再显示该表的数据。从而在library cache中放入一些数据。
SQL> create table sharedpool_test as select * from dba_objects where rownum<10;
SQL> select object_id,object_name from sharedpool_test;
以level 1转储整个library cache。
SQL> ALTER SESSION SET EVENTS 'immediate trace name library_cache level 1';
打开跟踪文件可以看到类似这样的信息,这实际就是v$librarycache里记录的信息,只不过v$librarycache中记录的是根据下面的信息合并汇总以后得到的。
namespace gets hit ratio pins hit ratio reloads invalids
-------------- --------- --------- --------- --------- ---------- ----------
CRSR 563 0.815 2717 0.916 15 0
TABL/PRCD/TYPE 403 0.730 568 0.653 0 0
BODY/TYBD 2 0.000 2 0.000 0 0
……………………
然后,我们分别以level 4、8、16、32分别对library cache进行转储,生成的转储文件分别以4#、8#、16#和32#来表示。
打 开4#文件,然后直接查找“select object_id,object_name from sharedpool_test”,因为我们前面说到过,对于SQL语句来说,整个SQL语句的文本就是library cache object的名称。于是,我们可以发现类似下图四所
示的内容:
图四
这里的BUCKET 62658就相当于图二中的2号bucket。该bucket上只挂了一个对象,其对象句柄号为6758cdbc。在这个对象句柄里存放了很多信息,这里 可以看到该对象的namespace为CRSR,也就是SQL AREA。可以看到该
SQL语句的hash值为541cf4c2,将其转换为十进制以后可以直接到v$sql中找到该SQL语句。我们还可以看到很复杂的 flags字段,它会包括很多标记,比如RON表示只读(Read Only),SML表示当前句柄尺寸比较小(Small)等
。而下面的lwt则表示正在等待lock的对象列表(Lock Waiters),对应图三中的“Lock Waiters”;ltm则表示临时正在持有lock的对象列表(Lock Temporary),对应图三中的“Lock Owners”;pwt则表示正在等待pin的对
象列表(Pin Waiters)对应图三中的“Pin Waiters”;ptm则表示临时正在持有pin的对象列表(Pin Temporary),对应图三中的“Pin Owners”。再往下看,可以看到CHILDREN部分,这部分就是前面所说过的子游标的信息
了。实际上,指向heap 0的指针也位于这一部分,这个指针也就是6758c840。
SQL> select sql_text from v$sql where hash_value=to_number('541cf4c2','xxxxxxxx');
SQL_TEXT
--------------------------------------------------------------------------------
select object_id,object_name from sharedpool_test
然后,我们打开8#文件,查找6758c840,可以看到如下图五所示的内容。这就是heap 0中所包含的内容。可以看到该heap 0的handle正是6758c840,type为CRSR。还可以看到几个重要的table,这些table都是我们前面介
绍过的,包括 DEPENDENCIES、ACCESSES、TRANSACTIONS。从前面我们已经知道dependency table记录的是SQL语句所依赖的对象,这里我们可以看到我们的SQL语句依赖一个对象,同时该对象的handle为 675d0d74,很明显,
它一定指向sharedpool_test表。同时,我们可以看到transaction table所记录的oracle底层解析的对象的handle也是675d0d74,它与dependency table所记录的对象是一样的,说明这个表是实实在在的表,而不是一个同名
词。
图五
于是我们继续在8#文件里查找675d0d74,也就是找到library cache中记录SQL所引用的对象的部分。
我们可以看到类似下图六所示的内容。从name列中可以看到,该对象正是sharedpool_test表,同时该表所在的schema为COST。而且从type为TABL也可以看到,对象sharedpool_test是一个表。
图六
我们再次回到图五,也就是记录heap 0的部分。我们可以看到最后一部分是DATA BLOCKS,从我们前面介绍过的内容可以知道这部分的记录指向了其他的heap内存块。我们从data#列上可以知道,该SQL存在两个相关的
heap,编号为0和6。我们知道,heap 0存放了SQL语句本身所涉及到的对象以及若干种表等的信息,而heap 6则存放了SQL语句的文本、执行计划等。于是,我们可以到32#文件中查找6758c7d0(heap 0)和67587c34(heap 6)
,如下图七所示。我们同时可以看到owner的值,实际上这正是在图五中的object的代号。同时从heap的name处也可以看到,heap 0为library cache,而heap 6为sql area,这也说明了这两个不同的heap所存放的不同内容。
图七
2.3 dictionary cache概述
dictionary cache专门用来存放SYS schema所拥有的对象的内存区域。使用dictionary cache时以行为单位,而不像其他比如buffer cache以数据块为单位,因此dictionary cache也叫做row cache。构造dictionary
cache的目的是为了加快解析SQL语句的速度,因为dictionary cache里存放了所有表的定义、Storage信息、用户权限信息、约束定义、回滚段信息、表的统计信息等。
而这些信息都是在解析过程中必须用到的。假设oracle在解析SQL的过程中,发现dictionary cache里没有该SQL所引用的表的定义信息,则oracle必须到磁盘上system表空间里找到这个引用表的定义信息,并将这些定义
信息加载到 dictionary cache里。这个从磁盘上获取数据字典数据的过程就叫做递归SQL(Recursive SQL)。通常来说,当我们执行一条新的SQL语句时,都会产生很多次的递归调用,也会产生很多的递归SQL。比如我们来下
面这个例子。
SQL> set autotrace traceonly stat;
SQL> select * from sharedpool_test;
Statistics
----------------------------------------------------------
185 recursive calls
0 db block gets
25 consistent gets
…………
从这里可以很明显看到执行该SQL产生了185次的递归调用,这185次的递归调用将表sharedpool_test相关的信息,比如列定义、统计信息 等,都加载到了dictionary cache里。当我们再次执行该SQL时,会发现recursive
calls变成了0,因为dictionary cache里已经包含解析SQL所需要参照的数据字典了。
转储dictionary cache
我们可以使用如下命令对dictionary cache进行转储。
ALTER SESSION SET EVENTS 'immediate trace name row_cache level N';
这里的N可以取的值分别为:
1 转储dictionary cache的统计信息 ;
2 转储hash表的汇总信息 ;
8 转储dictionary cache中的对象的结构信息;
如果对level 1进行转储,可以看到转储出来的内容,很明显,就是v$rowcache里的内容。每一种数据字典都有一行记录来表示。比如有tablespace相关的数据字典等。
如果以level 2转储的话,可以看到类似如下的内容。这里有23个hash表对dictionary cache中的对象进行管理,每个hash表都对应了一种数据字典,同时有一个名为row cache objects的latch来控制并发访问。可以看到,
v$latch_children里名为“row cache objects”的记录数量也是23。
ROW CACHE HASH TABLE: cid=0 ht=66BD90B0 size=32
…………
ROW CACHE HASH TABLE: cid=1 ht=66BD78B0 size=256
…………
ROW CACHE HASH TABLE: cid=22 ht=66DA5590 size=512
shared pool的内部管理机制
3.1解析SQL语句的过程
为了将用户写的可读的SQL文本转化为oracle认识的且可执行的语句,这个过程就叫做解析过程。
解析分为硬解析和软解析。当一句SQL第一次被执行时必须进行硬解析。
当客户端发出一条SQL语句(也可以是一个存储过程或者一个匿名PL/SQL块)进入shared pool时
(注 意,我们从前面已经知道,oracle对这些SQL不叫做SQL语句,而是称为游标(cursor)。因为oracle在处理SQL时,需要很多相关的辅 助信息,这些辅助信息与SQL语句一起组成了游标),oracle首先将SQL文本转化为
ASCII字符,然后根据hash函数计算其对应的hash值 (hash_value)。根据计算出的hash值到library cache中找到对应的bucket,然后比较bucket里是否存在该SQL语句。
如果不存在,则需要按照我们前面所描述的,获得 shared pool latch,然后在shared pool中的可用chunk链表(也就是bucket)上找到一个可用的chunk,然后释放shared pool latch。在获得了chunk以后,这块chunk就可
以认为是进入了library cache。然后,进行硬解析过程。硬解析包括以下几个步骤:
1) 对SQL语句进行语法检查,看是否有语法错误。比如没有写from等。如果有,则退出解析过程。
2) 到数据字典里校验SQL语句涉及的对象和列是否都存在。如果不存在,则退出解析过程。
3) 将对象进行名称转换。比如将同名词翻译成实际的对象等。如果转换失败,则退出解析过程。
4) 检查游标里用户是否具有访问SQL语句里所引用的对象的权限。如果没有权限,则退出解析过程。
5) 通过优化器创建一个最优的执行计划。这一步是最消耗CPU资源的。
6) 将该游标所产生的执行计划、SQL文本等装载进library cache的若干个heap中。
在 硬解析的过程中,进程会一直持有library cach latch,直到硬解析结束。硬解析结束以后,会为该SQL产生两个游标,一个是父游标,另一个是子游标。父游标里主要包含两种信息:SQL文本以及优化 目标(optimizer
goal)。父游标在第一次打开时被锁定,直到其他所有的session都关闭该游标后才被解锁。当父游标被锁定的时候是不能被交换出library cache的,只有在解锁以后才能被交换出library cache,这时该父游标对应的所有子
游标也被交换出library cache。子游标包括游标所有的信息,比如具体的执行计划、绑定变量等。前面图四中看到的CHILDREN部分就是子游标所对应的handle的信息。 子游标随时可以被交换出library cache,当子游标被交
换出library cache时,oracle可以利用父游标的信息重新构建出一个子游标来,这个过程叫reload。可以使用下面的方式来确定reload的比率:
SELECT 100*sum(reloads)/sum(pins) Reload_Ratio FROM v$librarycache;
一 个父游标可以对应多个子游标。子游标具体的个数可以从v$sqlarea的version_count字段体现出来。而每个具体的子游标则全都在v$ sql里体现。当具体的绑定变量的值与上次的绑定变量的值有较大差异(比如上次执行的
绑定变量的值的长度是6位,而这次执行的绑定变量的值的长度是200 位)时或者当SQL语句完全相同,但是所引用的对象属于不同的schema时,都会创建一个新的子游标。
如果在bucket中找到了该SQL语句,则说明该SQL语句以前运行过,于是进行软解析。软解析是相对于硬解析而言的,如果解析过程中,可以从硬解析的步骤中去掉一个或多个的话,这样的解析就是软解析。软解析分为以下三
种类型。
1) 第一种是某个session发出的SQL语句与library cache里其他session发出的SQL语句一致。这时,该解析过程中可以去掉硬解析中的5和6这两步,但是仍然要进行硬解析过程中的2、3、4步骤:也就是表名和列名检查、名称
转换和权限检查。
2) 第二种是某个session发出的SQL语句与library cache里该同一个session之前发出的SQL语句一致。这时,该解析过程中可以去掉硬解析中的2、3、5和6这四步,但是仍然要进行权限检查,因 为可能通过grant改变了该
session用户的权限。
3) 第三种是当设置了初始化参数session_cached_cursors时,当某个session对相同的cursor进行第三次访问时,将在该 session的PGA里创建一个标记,并且该游标即使已经被关闭也不会从library cache中交换出去。这样
,该session以后再执行相同的SQL语句时,将跳过硬解析的所有步骤。这种情况下,是最高效的解析方式,但是会消耗很大 的内存。
我们先来举一个例子说明如果在解析过程中发生语法或语义错误时,在shared pool中是怎样体现的。
SQL> select object_type fromm sharedpool_test111;
ORA-00942: 表或视图不存在
然后我们以level 16转储library cache,并打开转储文件,找到相应的部分,如下图八所示。可以看到,
该SQL 语句在语法上是错误的(from写成了fromm),oracle仍然在shared pool中为其分配了一个chunk,然后该chunk进入library cache,并在library cache中分配了一个bucket,同时也生成了heap 0,但是该heap 0中不
存在相应的一些如dependency table等table的部分,以及data block的部分。我看到有些资料上说SQL语句是先进行语法分析,如果通过语法分析以后,则应用hash函数生成hash值,然后再去shared pool中分配chunk。实际
上从这个实例已经可以看出,这个说法是错误的。oracle始终都是先对SQL生成hash值(不论该SQL语法上是否正 确),再根据hash值到对应的可用chunk链表(也就是bucket)里分配chunk,然后进入语法解析等解析过程。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/35489/viewspace-677002/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/35489/viewspace-677002/
更多推荐
所有评论(0)