一、游标

     我们要先说一下游标这个概念。

     从Oracle数据库管理员的角度上说,游标是对存储在库缓存中的可执行对象的统称。SQL语句是存储在库缓存中的,它是游标。除了它之外,还有Oracle的存储过程也是存储在库缓存中的可执行对象,从Oracle DBA的角度上说,它也是游标。Oracle也把它算为游标,在某些和游标相关的视图中,也会显示存储过程的一些信息的。但从开发者的角度说,只有SQL语句才是游标。

二、关于游标的视图

     你的应用程序或许是用Java、Pro*C等语言开发的,也可能有中件间,等等,对于DBA来说,我们不必过多的关心这些。以一个常见的三层应用为例, 如下图:

Oracle性能优化 之 游标及SQL_oracle

     类似的图我们在很多地方都可以看到,假设这是一个三层J2EE应用。客户端调用的Java应用程序存放在中间的应用服务器层,应用程序的执行由应用服务器负责。

Oracle性能优化 之 游标及SQL_数据库_02

  如上图这段Java应用程序,它的执行就是应用应用服务器的任务。但是,当执行到executeQuery ("select * from Test")语句时,这条Java语句要求从数据库服务器中查询表Test。发下图:

Oracle性能优化 之 游标及SQL_数据库_03

 这条语句的执行,是由数据库服务器负责的。数据库服务器只负责以最快的速度将“Select * from test”执行完毕。其他的它一概不负责。我们作为DBA,只要保证SQL语句可以更快的执行就行了,至于应用程序逻辑方面的问题,不由我们负责。也就是说,作为DBA,我们不必负责具体代码的问题,我们只负责SQL语句的执行。每条送交Oracle执行的SQL语句,无论这条语句是你手动在SQL*Plus命令窗口中敲入的,还是应用服务器传送给Oracle要求执行的,它们都以一样的方式被传递到Oracle中,由服务器进程执行。这些SQL语句的执行情况、具体的执行计划等数据资料会在一些视图中被记录下来,以供DBA追踪问题、调优SQL的执行。

下面,我们就介绍一下这些相关SQL执行情况的视图。我们再强调一个名词,对于从任何地方传递给Oracle数据库服务器要求执行的东西,我们都称为游标。它主要包括SQL语句和PL/SQL程序段。

1.V$SQL

SQL_TEXT:SQL语句的文本

 SQL_FULLTEXT:SQL语句的完全文本

 SQL_ID

 SHARABLE_MEM:游标所占共享内存

 PERSISTENT_MEM:游标持续期所占用的Fixed(固定)内存

 RUNTIME_MEM:游标在运行期所占用的Fixed(固定)内存

 SORTS:游标完成的排序次数

 LOADED_VERSIONS:游标在库缓存所占的内存堆是否被加载

 OPEN_VERSIONS:游标是否被锁定。

 USERS_OPENING:打开游标的会话数。也就是当前正在缓存游标到PGA中的会话数。游标被执行三次后,就会被缓存到PGA中。此数值就加1。

 FETCHES:抓取的次数

 EXECUTIONS:执行次数

 PX_SERVERS_EXECUTIONS:以并行方式执行的总次数

 END_OF_FETCH_COUNT:抓取全部行的次数

 USERS_EXECUTING:当前正在执行此游标的会话数

 LOADS:游标被加载或重新加载到库缓存中的次数。游标只所以被重新加载有可能是游标无效或库缓存内存不足。

 FIRST_LOAD_TIME:游标被第一次被加载的时间。也就是生成执行计划的时间

 INVALIDATIONS:游标的无效次数

 PARSE_CALLS:游标的解析次数,包括硬解析与软解析

 DISK_READS:游标执行了多少次物理读

 DIRECT_WRITES:游标直接写的次数

 BUFFER_GETS:逻辑读的次数

 APPLICATION_WAIT_TIME:应用程序的等待时间,单位微秒

 CONCURRENCY_WAIT_TIME:并行的等待时间,单位微秒

 CLUSTER_WAIT_TIME:Cluster等待时间

 USER_IO_WAIT_TIME:用户I/O等待时间

 PLSQL_EXEC_TIME:PL/SQL执行时间

 JAVA_EXEC_TIME:Java执行时间

 ROWS_PROCESSED:游标一共抓取了多少行。同样的行,每抓取一次此列都会增加

 COMMAND_TYPE:命令类型

 OPTIMIZER_MODE:优化器模式

 OPTIMIZER_COST:执行计划的成本

 OPTIMIZER_ENV:执行时的环境

 OPTIMIZER_ENV_HASH_VALUE:环境的HASH值

 PARSING_USER_ID:最先解析此游标的用户的ID

 PARSING_SCHEMA_ID:最先解析此游标的方案ID

 PARSING_SCHEMA_NAME:最先解析此游标的方案ID

 KEPT_VERSIONS:是否使用DBMS_SHARED_POOL包将游标Pin到库缓存中

 ADDRESS:父游标句柄的地址

 TYPE_CHK_HEAP

 HASH_VALUE:游标的HASH值

 OLD_HASH_VALUE:老HASH值

 PLAN_HASH_VALUE:执行计划的HASH值。(上述三个HASH值并不相同)

 CHILD_NUMBER:子游标数量

 SERVICE

 SERVICE_HASH

 MODULE:第一次解析游标的应用程序名。可以在应用程序中通过调用DBMS_APPLICATION_INFO.SET_MODULE设置。

 MODULE_HASH:应用程序名的HASH值

 ACTION:第一次解析时的动作名。可以在应用程序中通过调用DBMS_APPLICATION_INFO.SET_ACTION设置。

 ACTION_HASH:动作名的HASh值

 SERIALIZABLE_ABORTS:每个游标产生ORA-08177 errors错误(事务串行化无效)的次数。

 OUTLINE_CATEGORY:大纲类型

 CPU_TIME:游标解析、执行、抓取时所用的CPU时间。单位是微秒。

 ELAPSED_TIME:游标解析、执行、抓取时所用的总时间。单位是微秒。

 OUTLINE_SID:大纲会话的SID

 CHILD_ADDRESS:游标本身的地址

 SQLTYPE:游标所用的SQL语言的版本

 REMOTE:游标是否是远端映像的

 OBJECT_STATUS:对象状态

 LITERAL_HASH_VALUE:游标文本的HASH值

 LAST_LOAD_TIME:执行计划最后一次被加载到库缓存中的时间。

 IS_OBSOLETE:当子游标太多时,此子游标是否被荒废。

 CHILD_LATCH:保护游标的子闩编号

 SQL_PROFILE:SQL的概要文件

 PROGRAM_ID:过程ID

 PROGRAM_LINE#

 EXACT_MATCHING_SIGNATURE

 FORCE_MATCHING_SIGNATURE

 LAST_ACTIVE_TIME:最后一次使用执行计划的时间。

 BIND_DATA:绑定变量的信息

     这个视图中DISK_READS、BUFFER_GETS、CPU_TIME、ELAPSED_TIME这四个列在调优SQL语句时最为重要。在数据库系统的速度不是太另人满意时,如果你已经确定过了,不是其他方面的原因,而是SQL语句性能的问题,只是无法确定是那条、或那些条语句拖慢了整体的速度。那么此时选择调优物理读、逻辑读最多的,或最耗CPU时间的SQL语句进行调节,往往可以取得今人满意的性能增长。

     我们也可以以EXECUTIONS(执行次数)最多的SQL语句为调优对象。另外,PARSE_CALLS是解析次数,对于此列值最多的SQL语句,我们可以看看是否可以降低语句的解析次数。

     关于SQL调优,和程序的调优是一样的。如果我们从事过代码优化这样的工作,就会知道,对于一个大型的应用程序来说调优的方法也是要从执行次数最多的那部分代码、或从最消耗资源的代码入手。

     还有一个问题,就是文档中关于这个视图会经常提到一个概念:子游标与父游标。如果两个游标的文本一模一样,但由于环境不同,比如,游标所操作的表是不同用户下的同名表,这两个游标是不能共享执行计划的。它们都有各自的执行计划存在库缓存中。这两个游标就是子游标,Oracle还会建立一个父游标,父游标中没有执行计划,它只是文本相同但执行计划不同的所有游标的代表。

     其实在库缓存中,即使没有文本相同的子游标,Oracle会为每个游标都创建父游标。因为父游标是文本相同的子游标的代表吗,所有文本相同的游标共享同一个父游标。

     也就是说,只要你执行SQL语句,Oracle都会在库缓存中保存一父一子两个游标。如果你执行了文本相同但环境不同因而不能共享执行计划的SQL语句,那么一个父游标可能就对应多个子游标。

     父游标没有执行计划,它只有一信息管理性数据,Oracle添加它的目的就是为了管理文本相同的游标。有一个视图是专门针对父游标的,就是V$sqlarea。下面我们说一下这个视图。

 

2. V$SQLAREA

     V$SQLAREA和V$SQL的列几乎是一模一样的。在V$SQLAREA中汇总了子游标的数据。如果有两个语句:语句A和语句B,它们文本一模一样,但是由于环境不同没有共享执行计划,而是有各自的执行计划。也就是语句A和语句B是同一父游标下的子游标。在V$SQL视图中,因为它是显示子游标的,所以语句A和语句B各占一行,假设语句A的DISK_READS(物理读)是100,语句B的物理读是3000。V$SQLAREA是显示父游标信息的,语句A和语句B因为文本相同,它们两个对应同一个父游标,在V$SQLAREA中占一行。在V$SQLAREA中,语句A和语句B父游标行中的DISK_READS就是3100,也就是语句A和语句B的和。V$SQLAREA中的其它列也是如此,都是V$SQL中相应子游标的合计。

     有一个列是V$SQL中没有的,就是:VERSION_COUNT,它是对应同一父游标的子游标的数量。如果这个数字太高,可能代表由于某些原因使本可以共享执行计划的游标没有共享。

 

3.V$open_cursor与Open_cursor参数

 这个视图和参数涉及游标的打开。什么是游标的打开,就是在库缓存中,用户在软、硬解析游标时,会在游标对象的句柄上加一个锁,也就是Library cache lock。在解析并执行完游标后,这个锁并不会马上去掉,而是会一直保留着,直到用户发出了Close命令关闭游标时为止。我们在SQL*Plus命令窗口中发出的命令,在抓取完所有行后,SQL*Plus将自动为我们发出Close命令来关闭游标。

 当游标打开时,Library cache lock将一直保持,这样,即使库缓存内存紧张,需要老化对象,也不会老化这些还正在加锁的对象。因此,如果用户不停的要求数据库服务器打开游标、执行SQL,但却忘了关闭游标,这很容易耗尽共享池的内存。为此,Oracle准备了一个参数,就是Open_cursor,它的默认值在9i下是50,在10g中是300,也就是说,在10g下,每个会话最多只能同时打开300个游标。有了这个限制,就不用害怕用户不停的打开游标但又不关闭它,而耗尽共享池内存了。

     如果会话同时打开的游标数量超出了Open_cursor参数的限制,Oracle将禁止会话打开新的游标。同时报出错误:ORA-01000: 超出打开游标的最大数 。

     在用户断开会话的连接后,会话打开的这些游标将自动关闭。

     V$open_cursor视图专用来查看当前会话打开的游标信息。它只能查看当前会话打开的游标。

4.CURSOR_SHARING参数

如果应用程序中有很多类似下面这样的SQL语句:

select * from 某表 where id=1;

select * from 某表 where id=2;

select * from 某表 where id=50;

     等等,这些SQL语句严格来说是无法共享游标(也就是共享执行计划)的,但是这些语句所需要执行计划其实都是一样的。无论你在表中查询ID为1的行还是查询ID为100的行,执行方式应该是一样的。如果你想让这样的语句共享游标,那么,你可以改变Cursor_sharing参数的值。

此参有三个值:

Ÿ EXACT:这个值是默认值。除非游标文本一模一样,否则不会共享游标。

Ÿ SIMILAR:这个最智能,如果游标只有条件中的数据值部分不同,并且库缓存中原有游标的执行计划对于新执行的SQL语句也是最优的,将不再为SQL语句创建新的游标,而是让它共享库缓存中原有的游标。

Ÿ FORCE :不比较执行计划是否最优,只要游标中除了条件中的数据值部分不同外,其他部分都相同,就会共享游标。

     此参数可以在会话级修改,也就是可以使用Alter session修改它的值,这将只影响某一个会话,而不会影响其他会话。

-