如何诊断oracle数据库运行缓慢或hang住的问题

为了诊断oracle运行缓慢的问题首先要决定收集哪些论断信息,可以采取下面的诊断方法:
1.数据库运行缓慢这个问题是常见还是在特定时间出现
如果数据库运行缓慢是一个常见的问题那么可以在问题出现的时候收集这个时期的awr或者statspack报告(通常收集时间间隔是一个小时).生成awr报告的方法如下:
awr是通过sys用户来收集持久系统性能统计信息并且这些信息保存在sysaux表空间.缺省情况下快照是一个小时生成一次并且保留7天.awr报告输出了基于指定快照之间的一系列的统计信息用于性能分析和调查其它问题.
运行基本的报告
可以执行下面的脚本来生成一个awr报告:
$ORACLE_HOME/rdbms/admin/awrrpt.sql

可以根据自己收集awr报告的原因来决定生成一个快照的时间间隔也可以指定生成awr报告的格式(text或html).

生成各种类型的awr报告
可以根据各种要求来运行各种sql脚本来生成各种类型的awr报告.每一种报告都有两种格式(txt或html):
awrrpt.sql
显示指定快照范围内的各种统计信息

awrrpti.sql
显示一个特定数据库和实例中指定快照范围内的各种统计信息

awrsqrpt.sql
显示一个指定快照范围内的一个特定的sql语句的统计信息.运行这个报告是为了检查或调查一个特定sql语句的性能

awrsqrpi.sql
显示一个特定sql在指定快照范围内的的统计信息.

awrddrpt.sql
比较在两个选择的时间间隔期间内详细的性能数据和配置情况

awrddrpi.sql
在一个特定的数据库和平共处实例中比较在两个选择的时间间隔期间内详细的性能数和配置情况

各种awr相关的操作
怎样修改awr快照的设置:

BEGIN
  DBMS_WORKLOAD_REPOSITORY.modify_snapshot_settings(
    retention => 43200,        -- Minutes (43200 = 30 Days).
                               -- Current value retained if NULL.
    interval  => 30);          -- Minutes. Current value retained if NULL.
END;
/

创建一个awr基线:

BEGIN
  DBMS_WORKLOAD_REPOSITORY.create_baseline (
    start_snap_id => 10,
    end_snap_id   => 100,
    baseline_name => 'AWR First baseline');
END;
/

在oracle11G中引入了一个新的dbms_workload_repository.create_baseline_template过程来创建一个awr基线模板

BEGIN
DBMS_WORKLOAD_REPOSITORY.CREATE_BASELINE_TEMPLATE (
start_time => to_date('&start_date_time','&start_date_time_format'),
end_time => to_date('&end_date_time','&end_date_time_format'),
baseline_name => 'MORNING',
template_name => 'MORNING',
expiration => NULL ) ;
END;
/

“expiration=>NULL”这意味着这个基线将永远保持有效.

删除一个awr基线

BEGIN
    DBMS_WORKLOAD_REPOSITORY.DROP_BASELINE (
    baseline_name => 'AWR First baseline');
END;
/

也能从一个老的数据库中删除一个awr基线:

BEGIN
DBMS_WORKLOAD_REPOSITORY.DROP_BASELINE (baseline_name => 'peak baseline',
cascade => FALSE, dbid => 3310949047);
END;
/

删除awr快照:

BEGIN
  DBMS_WORKLOAD_REPOSITORY.drop_snapshot_range(
(low_snap_id=>40,
High_snap_id=>80);
END;
/

也可能基于报告时间期间对创建和删除的awr基线指定一个模板:

BEGIN
DBMS_WORKLOAD_REPOSITORY.CREATE_BASELINE_TEMPLATE (
day_of_week => 'MONDAY',
hour_in_day => 9,
duration => 3,
start_time => to_date('&start_date_time','&start_date_time_format'),
end_time => to_date('&end_date_time','&end_date_time_format'),
baseline_name_prefix => 'MONDAY_MORNING'
template_name => 'MONDAY_MORNING',
expiration => 30 );
END;
/

将会在’&start_date_time’到’&end_date_time’期间的每一个星期一都会生成基线

手动生成的一个awr快照:

BEGIN
  DBMS_WORKLOAD_REPOSITORY.create_snapshot();
END;
/

工作负载资料档案库视图:
V$ACTIVE_SESSION_HISTORY – 显示历史活动会话信息每秒抽样一样
V$METRIC – 显示度量标准信息
V$METRICNAME – 显示与每个度量标准组相关的度量标准
V$METRIC_HISTORY – 显示历史度量标准
V$METRICGROUP – 显示所有的度量标准组
DBA_HIST_ACTIVE_SESS_HISTORY – 显示历史活动会话的详细信息
DBA_HIST_BASELINE – 显示基线信息
DBA_HIST_DATABASE_INSTANCE – 显示数据库环境信息
DBA_HIST_SNAPSHOT – 显示快照信息
DBA_HIST_SQL_PLAN – 显示sql执行计划
DBA_HIST_WR_CONTROL – 显示awr设置情况

如果数据库运行缓慢在特定时间出现那么可以当问题存在时生成一个awr或statspack报告,报告的时间间隔包含了问题出现的时间.另外为了比较可以收集没有出现问题而时间间隔相同的数据库正常运行的报告这样可以对报告进行比较.

2.数据库缓慢它影响的是一个会话,几个会话还是所有会话
如果数据库缓慢它影响的是一个会话或几个会话可以对这个会话或几个会话进行10046跟踪
如果数据库缓慢它影响的是所有会话可以收集awr或statspack报告

执行10046跟踪的方法如下:
收集10046跟踪文件
10046事件是一种标准的方法用来对oracle会话收集扩展的sql_trace信息
对于查询性能问题来说通常要求记录查询的等待和绑定变量信息.这可以使用级别为12的10046跟踪来完成.下面的例子说明了在各种情况下设置10046事件.

跟踪文件的位置
在oracle11g及以上版本中引入了新的诊断架构,跟踪和核心文件存储的位置由diagnostic_dest初始化参数来控制.可以使用下面的命令来显示:

sys@JINGYONG> show parameter diagnostic_dest

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
diagnostic_dest                      string      /u01/app/oracle

注意:在有些例子中可能设置了’tracefile_identifier’来帮助找到输出的跟踪文件

会话跟踪
可以在用户会话执行sql语句之前对会话启用跟踪,在会话级别收集10046跟踪


sys@JINGYONG> alter session set timed_statistics=true;

会话已更改。

sys@JINGYONG> alter session set statistics_level=all;

会话已更改。

sys@JINGYONG> alter session set max_dump_file_size=unlimited;

会话已更改。

sys@JINGYONG> alter session set events '10046 trace name context forever,level 1
2';

会话已更改。

sys@JINGYONG> select * from dual;

D
-
X

sys@JINGYONG>exit

如果会话没有退出可以执行以下语句来禁用10046跟踪

sys@JINGYONG> alter session set events '10046 trace name context off';

会话已更改。


sys@JINGYONG> select * from v$diag_info ;

   INST_ID NAME
---------- ----------------------------------------------------------------
VALUE
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------
         1 Diag Enabled
TRUE

         1 ADR Base
/u01/app/oracle

         1 ADR Home
/u01/app/oracle/diag/rdbms/jingyong/jingyong

         1 Diag Trace
/u01/app/oracle/diag/rdbms/jingyong/jingyong/trace

         1 Diag Alert
/u01/app/oracle/diag/rdbms/jingyong/jingyong/alert

         1 Diag Incident
/u01/app/oracle/diag/rdbms/jingyong/jingyong/incident

         1 Diag Cdump
/u01/app/oracle/diag/rdbms/jingyong/jingyong/cdump

         1 Health Monitor
/u01/app/oracle/diag/rdbms/jingyong/jingyong/hm

         1 Default Trace File
/u01/app/oracle/diag/rdbms/jingyong/jingyong/trace/jingyong_ora_2572_10046.trc

         1 Active Problem Count
0

         1 Active Incident Count
0


已选择11行。

sys@JINGYONG> select * from v$diag_info where name='Default Trace File';

   INST_ID NAME
---------- ----------------------------------------------------------------
VALUE
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------
         1 Default Trace File
/u01/app/oracle/diag/rdbms/jingyong/jingyong/trace/jingyong_ora_2572_10046.trc

注意:如果会话不是彻底的关闭和禁用跟踪那么重要的跟踪信息可能会从跟踪文件中丢失.

注意:这里statistics_level=all因此它会在这种情况下收集一定程度的统计信息.这个参数有三个参数值.all,typical,basic.为了诊断性能问题会要求获得一定程度的统计信息.设置为all可能是不必要的但可以使用typical以此来获得全面的诊断信息.

跟踪一个已经启动的进程
如果要跟踪一个已经存在的会话可以使用oradebug来连接到会话初始化10046跟踪
1.通过某种方法来确定要被跟踪的会话
例如在sql*plus中启动一个会话然后找到这个会话的操作系统进行id(spid):
select p.PID,p.SPID,s.SID
from v$process p,v$session s
where s.paddr = p.addr
and s.sid = &SESSION_ID
/

SPID是操作系统进程标识符
PID是oracle进程标识符
如果你不知道要不得被跟踪会话的sid可以使用类似于下面的查询来帮助你识别这个会话:

column line format a79
set heading off
select 'ospid: ' || p.spid ||'  pid: '||p.pid || ' # ''' ||s.sid||','||s.serial#||''' '||
  s.osuser || ' ' ||s.machine ||' '||s.username ||' '||s.program line
from v$session s , v$process p
where p.addr = s.paddr
and s.username <> ' ';
执行结果如下:
sys@JINGYONG> column line format a79
sys@JINGYONG> set heading off
sys@JINGYONG> select 'ospid: ' || p.spid || ' # ''' ||s.sid||','||s.serial#||'''
 '||
  2    s.osuser || ' ' ||s.machine ||' '||s.username ||' '||s.program line
  3  from v$session s , v$process p
  4  where p.addr = s.paddr
  5  and s.username <> ' ';

ospid: 2529 # '30,32' Administrator WORKGROUP\JINGYONG SYS sqlplus.exe

注意:在oracle12c中对于多线程进程,在v$process视图中加入了新的列stid来找到特定的线程.因为oracle会组合多个进程到一个单独的ospid中.为了找到这个特定的线程使用下面的语法:
oradebug setospid

2.当确定进程的操作系统进程ID后然后可以使用下面的语句来初始化跟踪:
假设要被跟踪进程的操作系统进程ID是2529

SQL>connect / as sysdba
sys@JINGYONG> oradebug setospid 2529
Oracle pid: 21, Unix process pid: 2529, image: oracle@jingyong
sys@JINGYONG> oradebug unlimit
已处理的语句
sys@JINGYONG> oradebug event 10046 trace name context forever,level 12
已处理的语句
sys@JINGYONG> select * from dual;

X

sys@JINGYONG> oradebug event 10046 trace name context off
已处理的语句
sys@JINGYONG> oradebug tracefile_name
/u01/app/oracle/diag/rdbms/jingyong/jingyong/trace/jingyong_ora_2529.trc

注意:连接到一个会话也可以使用oradebug setorapid
在这种情况下PID(oracle进程标识符)将被使用(而不是使用SPID):
sys@JINGYONG> oradebug setorapid 21
Oracle pid: 21, Unix process pid: 2529, image: oracle@jingyong
从显示的信息可知道使用oradebug setorapid 21与oradebug set0spid 2529是一样的
sys@JINGYONG> oradebug unlimit
已处理的语句
sys@JINGYONG> oradebug event 10046 trace name context forever,level 12
已处理的语句
sys@JINGYONG> select sysdate from dual;

11-11月-13

sys@JINGYONG> oradebug event 10046 trace name context off
已处理的语句
sys@JINGYONG> oradebug tracefile_name
/u01/app/oracle/diag/rdbms/jingyong/jingyong/trace/jingyong_ora_2529.trc

注意:在oracle12c中对于多线程进程,在v$process视图中加入了新的列stid来找到特定的线程.因为oracle会组合多个进程到一个单独的ospid中.为了找到这个特定的线程使用下面的语法:
oradebug setospid

跟踪产生的跟踪文件名称类似于_.trc

实例级别的跟踪
注意:在实例级别启用跟踪因为每一个会话都会被跟踪这样对性能是有影响的
在设置这个跟踪参数后产生的每一个会话都会被跟踪断开的会话将不会被跟踪
设置系统级别的10046跟踪是用于当出现了一个问题会话但不能提前识别这个会话的情况下.在这种情况下跟踪可以被短时间地启用,这个问题可能会记录到跟踪文件中然后禁用跟踪在生成的跟踪文件中找到这个问题的原因

启用系统级别的10046跟踪:
alter system set events ‘10046 trace name context forever,level 12’;
对所有会话禁有系统级别的10046跟踪:
alter system set events ‘10046 trace name context off’;

初始化参数的设置:
当实例重新启动后对每一个会话启用10046跟踪.
event=”10046 trace name context forever,level 12″
要禁用实例级别的10046跟踪可以删除这个初始化参数然后重启实例或者使用alter system语句
alter system set events ‘10046 trace name context off’;

编写登录触发器
在有些情况下可能要跟踪特定用户的会话活动在这种情况下可以编写一个登录触发器来实现例如:

CREATE OR REPLACE TRIGGER SYS.set_trace
AFTER LOGON ON DATABASE
WHEN (USER like '&USERNAME')
DECLARE
lcommand varchar(200);
BEGIN
EXECUTE IMMEDIATE 'alter session set tracefile_identifier=''From_Trigger''';
EXECUTE IMMEDIATE 'alter session set statistics_level=ALL';
EXECUTE IMMEDIATE 'alter session set max_dump_file_size=UNLIMITED';
EXECUTE IMMEDIATE 'alter session set events ''10046 trace name context forever, level 12''';
END set_trace;
/

注意:为了能跟踪会话用户执行触发器需要显式的被授予’alter session’权限:
grant alter session to username;

使用SQLT来收集跟踪信息
什么是SQLTXPLAIN(SQLT)
SQLTXPLAIN也叫作SQLT,它是由专业的oracle服务技术中心提供了一个工具.SQLT输入一个SQL语句后它会输出一组诊断文件.这些诊断文件会被用来诊断性能低下的sql语句.SQLT连接到数据库并收集执行,基于成本优化的统计信息,方案对象元数据,性能统计,配置参数和类似影响SQL性能的元素.

使用SQLTXPLAIN的Xecute选项可以生成10046跟踪作为SQLT输出的一部分.

使用dbms_monitor包来进行跟踪
dbms_monitor是一个新的跟踪包.跟踪基于特定的客户端标识符或者服务名,模块名和操作名的组合形式来启用诊断和工作负载管理.在有些情况下可能会生成多个跟踪文件(例如对于一个模块启用服务级别的跟踪)使用新的trcsess工具来扫描所有的跟踪文件并将它们合成一个跟踪文件.在合并这一组跟踪文件后可以使用标准跟踪文件分析方法进行分析

查看启用的跟踪
可以查询dba_enabled_traces来检测什么跟踪被启用了.
例如:

sys@JINGYONG>select trace_type, primary_id, QUALIFIER_ID1, waits, binds
             from DBA_ENABLED_TRACES;

TRACE_TYPE                   PRIMARY_ID  QUALIFIER_ID1           WAITS        BINDS
---------------------- ---------------   ------------------      --------    -------
SERVICE_MODULE         SYS$USERS        SQL*Plus                 TRUE        FALSE
CLIENT_ID              HUGO                                      TRUE        FALSE
SERVICE                v101_DGB                                  TRUE        FALSE

在这个数据库中已经启用了三个不同的跟踪状态
1.第一行记录显示将会对在SQL*Plus中执行的所有sql语句进行跟踪
2.第二行记录显示将会对带有客户端标识符”HUGO’的所有会话进行跟踪
3.第三行记录显示将会对使用服务”v101_DGB’连接到数据库的所有程序进行跟踪

session_trace_enable函数
可以使用session_trace_enable过程来对本地实例的一个指定的数据库会话启用sql跟踪.
语法如下:
启用sql跟踪
dbms_monitor.session_trace_enable(session_id => x, serial_num => y,
waits=>(TRUE|FALSE),binds=>(TRUE|FALSE) );

禁止sql跟踪
dbms_monitor.session_trace_disable(session_id => x, serial_num => y);
其中waits的缺省值是true,binds的缺省值是false.

可以从v$session视图中查询会话id和序列号

SQL> select serial#, sid , username from v$session;

SERIAL#             SID  USERNAME
-------           -----  --------------
  1                 131
 18                 139
  3                 140
 11                 143     SCOTT

然后可以使用下面的命令来对指定的会话启用跟踪
SQL> execute dbms_monitor.session_trace_enable(143,11);
跟踪状态在数据库重启后就会被删除可以查询dba_enabled_traces视图看到没有记录
sys@JINGYONG> oradebug tracefile_name
/u01/app/oracle/diag/rdbms/jingyong/jingyong/trace/jingyong_ora_2529.trc
sys@JINGYONG> select trace_type,primary_id,qualifier_id1,waits,binds
2 from dba_enabled_traces;

未选定行

当会话断开或者使用下面的命令可以禁止跟踪
SQL> execute dbms_monitor.session_trace_disable(143,11);

client_id_trace_enable函数
在多层架构环境中,一个请求从一个终端客户端通过中间层分发到不同的数据库会话.这意味着终端客户端与数据库会话的联系不是静态的.在oracle10g之前没有方法可以对一个客户端跨不同数据库会话进行跟踪.端到端的跟踪可以通过一个新的属性client_identifier来标识它是唯一标识一个特定的终端客户端.这个客户端标识符对应于v$session视图中的client_identifier列.通过系统上下文也可以查看.
语法如下:
启用跟踪
execute dbms_monitor.client_id_trace_enable ( client_id =>’client x’,
waits => (TRUE|FALSE), binds => (TRUE|FALSE) );

禁止跟踪
execute dbms_monitor.client_id_trace_disable ( client_id =>’client x’);
其中waits的缺省值是true,binds的缺省值是false.

例如:
可以使用dbms_session.set_identifier函数来设置client_identifier

sys@JINGYONG> exec dbms_session.set_identifier('JY');

PL/SQL 过程已成功完成。

sys@JINGYONG> select sys_context('USERENV','CLIENT_IDENTIFIER') client_id from dual;

JY


sys@JINGYONG> select client_identifier client_id from v$session where sid=30;

JY

sys@JINGYONG> exec dbms_monitor.client_id_trace_enable('JY');

PL/SQL 过程已成功完成。

使用查询来检查跟踪是否已经启用

sys@JINGYONG> select primary_id,qualifier_id1,waits,binds
  2  from dba_enabled_traces where trace_type='CLIENT_ID';
PRIMARY_ID         QUALIFIER_ID1         WAITS    BINDS
----------------   --------------        -------- --------
JY                                       TRUE     FALSE

这个跟踪在数据库重启之后还是有效的你得调用函数来禁用.
sys@JINGYONG> exec dbms_monitor.client_id_trace_disable(‘JY’);

PL/SQL 过程已成功完成。
检查生成的跟文件

Trace file /u01/app/oracle/diag/rdbms/jingyong/jingyong/trace/jingyong_ora_2529.trc
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
ORACLE_HOME = /u01/app/oracle/product/11.2.0/db
System name:	Linux
Node name:	jingyong
Release:	2.6.18-164.el5
Version:	#1 SMP Tue Aug 18 15:51:54 EDT 2009
Machine:	i686
Instance name: jingyong
Redo thread mounted by this instance: 1
Oracle process number: 21
Unix process pid: 2529, image: oracle@jingyong


*** 2013-11-11 11:31:56.737
*** SESSION ID:(30.32) 2013-11-11 11:31:56.737
*** CLIENT ID:() 2013-11-11 11:31:56.737
*** SERVICE NAME:(jingyong) 2013-11-11 11:31:56.737
*** MODULE NAME:(sqlplus.exe) 2013-11-11 11:31:56.737
*** ACTION NAME:() 2013-11-11 11:31:56.737

PARSING IN CURSOR #8 len=96 dep=0 uid=0 oct=3 lid=0 tim=1384150635839986 hv=3018843459 ad='275fa5ec' sqlid='3gg23wktyzta3'
select primary_id,qualifier_id1,waits,binds
from dba_enabled_traces where trace_type='CLIENT_ID'
END OF STMT

在启用跟踪后执行的语句被记录到了跟踪文件中.

sys@JINGYONG> select primary_id,qualifier_id1,waits,binds
  2  from dba_enabled_traces where trace_type='CLIENT_ID';

未选定行

当你使用MTS时有时将会生成多个跟踪文件,不同的共享服务器进程能执行sql语句这就将会生成多个跟踪文件.对于RAC
环境也是一样.

serv_mod_act_trace_enable函数
端到端跟踪对于使用MODULE,ACTION,SERVICES标识的应用程序能够进行有效地管理和计算其工作量.service名,module和
action名提供了一种方法来识别一个应用程序中重要的事务.
你可以使用serv_act_trace_enable过程来对由一组service,module和action名指定的全局会话启用sql跟踪,除非指定了特定
的实例名.对于一个会话的service名,module名与v$session视图中的service_name和module列相对应.
语句如下:
启用跟踪
execute dbms_monitor.serv_mod_act_trace_enable(‘Service S’, ‘Module M’, ‘Action A’,
waits => (TRUE|FALSE), binds => (TRUE|FALSE), instance_name => ‘ORCL’ );

禁止跟踪
execute dbms_monitor.serv_mod_act_trace_disable(‘Service S’, ‘Module M’, ‘Action A’);
其中waits的缺省值是true,binds的缺省值是false,instance_name的缺省值是null.

例如想要对在数据库服务器使用SQL*Plus执行的所有sql语句进行跟踪可以执行以下命令:

sys@JINGYONG> select module,service_name from v$session where sid=25;
MODULE                                      SERVICE_NAME
-----------------------------               ---------------------
sqlplus@jingyong (TNS V1-V3)                SYS$USERS

sys@JINGYONG> exec dbms_monitor.serv_mod_act_trace_enable('SYS$USERS','sqlplus@j
ingyong (TNS V1-V3)');

PL/SQL 过程已成功完成。

sys@JINGYONG> select primary_id,qualifier_id1,waits,binds
  2  from dba_enabled_traces
  3  where trace_type='SERVICE_MODULE';
PRIMARY_ID       QUALIFIER_ID1                WAITS    BINDS
---------------  -------------------          -------- --------
SYS$USERS        sqlplus@jingyong (TNS V1-V3) TRUE     FALSE


启用跟踪后我们执行一个测试语句
SQL> select 'x' from dual;

'
-
x
检查生成的跟踪文件名
SQL> select * from v$diag_info where name='Default Trace File';

   INST_ID NAME
---------- ----------------------------------------------------------------
VALUE
--------------------------------------------------------------------------------
         1 Default Trace File
/u01/app/oracle/diag/rdbms/jingyong/jingyong/trace/jingyong_ora_4411.trc

查看跟踪内容如下

trace file /u01/app/oracle/diag/rdbms/jingyong/jingyong/trace/jingyong_ora_4411.trc
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
ORACLE_HOME = /u01/app/oracle/product/11.2.0/db
System name:	Linux
Node name:	jingyong
Release:	2.6.18-164.el5
Version:	#1 SMP Tue Aug 18 15:51:54 EDT 2009
Machine:	i686
Instance name: jingyong
Redo thread mounted by this instance: 1
Oracle process number: 24
Unix process pid: 4411, image: oracle@jingyong (TNS V1-V3)


*** 2013-11-11 14:34:00.971
*** SESSION ID:(25.412) 2013-11-11 14:34:00.972
*** CLIENT ID:() 2013-11-11 14:34:00.972
*** SERVICE NAME:(SYS$USERS) 2013-11-11 14:34:00.972
*** MODULE NAME:(sqlplus@jingyong (TNS V1-V3)) 2013-11-11 14:34:00.972
*** ACTION NAME:() 2013-11-11 14:34:00.972

WAIT #1: nam='SQL*Net message from client' ela= 152965072 driver id=1650815232 #bytes=1 p3=0 obj#=-1 tim=1384151640937525
CLOSE #1:c=1000,e=521,dep=0,type=0,tim=1384151640973430
=====================
PARSING IN CURSOR #1 len=20 dep=0 uid=0 oct=3 lid=0 tim=1384151640977682 hv=2740543121 ad='275fa9e4' sqlid='04vfkrajpkrnj'
select 'x' from dual

我们执行的测试语句被记录了在跟踪文件中.

sys@JINGYONG> exec dbms_monitor.serv_mod_act_trace_disable('SYS$USERS','sqlplus@
jingyong (TNS V1-V3)');

PL/SQL 过程已成功完成。

sys@JINGYONG> select primary_id,qualifier_id1,waits,binds
  2  from dba_enabled_traces
  3  where trace_type='SERVICE_MODULE';

未选定行

使用trcsess来合并跟踪文件
从某些跟踪操作中会得到多个跟踪文件.在oracle10g之前的版本中你得手动将这些跟踪文件合并到一起.现在可以使用trcsess工具来帮你合并这些跟踪文件.
语句如下:
trcsess [output=] [session=] [clientid=] [service=] [action=] [module=]

output= output destination default being standard output.
session= session to be traced.
Session id is a combination of session Index & session serial number e.g. 8.13.

clientid= clientid to be traced.
service= service to be traced.
action= action to be traced.
module= module to be traced.
Space separated list of trace files with wild card ‘*’ suppor
ted.

[oracle@jingyong trace]$ trcsess output=jingyong_ora_88888888.trc service=jingyong jingyong_ora_2529.trc jingyong_ora_4411.trc
[oracle@jingyong trace]$ ls -lrt jingyong_ora_88888888.trc
-rw-r–r– 1 oracle oinstall 16219 Nov 11 14:59 jingyong_ora_88888888.trc

dbms_application_info
可以在过程开始一个事务之前使用dbms_application_info.set*过程来注册一个事务名/客户端信息/模块名为以后检查性能来使用.你应该对以后可能消耗你最多系统资源的活动事务进行指定.
dbms_application_info包有以下过程
SET_CLIENT_INFO ( client_info IN VARCHAR2 );
SET_ACTION ( action_name IN VARCHAR2 );
SET_MODULE ( module_name IN VARCHAR2, action_name IN VARCHAR2 );

例如
sys@JINGYONG> create table emp as select * from scott.emp where 1=0;

表已创建。

sys@JINGYONG> exec dbms_application_info.set_module(module_name=>’add_emp’,actio
n_name=>’insert into emp’);

PL/SQL 过程已成功完成。

sys@JINGYONG> insert into emp select * from scott.emp;

已创建14行。

sys@JINGYONG> commit;

提交完成。

sys@JINGYONG> exec dbms_application_info.set_module(null,null);

PL/SQL 过程已成功完成。
下面查询v$sqlarea视图使用module和action列进行查询
sys@JINGYONG> select sql_text from v$sqlarea where module=’add_emp’;

insert into emp select * from scott.emp

sys@JINGYONG> select sql_text from v$sqlarea where action=’insert into emp’;

insert into emp select * from scott.emp

declare
l_client varchar2(100);
l_mod_name varchar2(100);
l_act_name varchar2(100);
begin
dbms_application_info.read_client_info(l_client);
dbms_application_info.read_module(l_mod_name,l_act_name);
dbms_output.put_line(l_client);
dbms_output.put_line(l_mod_name);
end;

dbms_session包:只能跟踪当前会话,不能指定会话。
跟踪当前会话:
SQL> exec dbms_session.set_sql_trace(true);
SQL> 执行sql
SQL> exec dbms_session.set_sql_trace(false);
dbms_session.set_sql_trace相当于alter session set sql_trace,从生成的trace文件可以明确地看alter session set sql_trace语句。
使 用dbms_session.session_trace_enable过程,不仅可以看到等待事件信息还可以看到绑定变量信息,相当于alter session set events ‘10046 trace name context forever, level 12’;语句,从生成的trace文件可以确认。
SQL> exec dbms_session.session_trace_enable(waits=>true,binds=>true);
SQL> 执行sql
SQL> exec dbms_session.session_trace_enable(); –This procedure resets the session-level SQL trace for the session from which it was called.

dbms_support包:不应该使用这种方法,非官方支持。
系统默认没有安装这个包,可以手动执行$ORACLE_HOME/rdbms/admin/bmssupp.sql脚本来创建该包。
SQL> desc dbms_support
FUNCTION MYSID RETURNS NUMBER
FUNCTION PACKAGE_VERSION RETURNS VARCHAR2
PROCEDURE START_TRACE
Argument Name Type In/Out Default?
—————————— ———————– —— ——–
WAITS BOOLEAN IN DEFAULT
BINDS BOOLEAN IN DEFAULT
PROCEDURE START_TRACE_IN_SESSION
Argument Name Type In/Out Default?
—————————— ———————– —— ——–
SID NUMBER IN
SERIAL NUMBER IN
WAITS BOOLEAN IN DEFAULT
BINDS BOOLEAN IN DEFAULT
PROCEDURE STOP_TRACE
PROCEDURE STOP_TRACE_IN_SESSION
Argument Name Type In/Out Default?
—————————— ———————– —— ——–
SID NUMBER IN
SERIAL NUMBER IN
SQL> select dbms_support.package_version from dual;
PACKAGE_VERSION
——————————————————————————–
DBMS_SUPPORT Version 1.0 (17-Aug-1998) – Requires Oracle 7.2 – 8.0.5
SQL> select dbms_support.mysid from dual;
MYSID
———-
292
SQL> select * from v$mystat where rownum=1;
SID STATISTIC# VALUE
———- ———- ———-
292 0 1
跟踪当前会话:
SQL> exec dbms_support.start_trace
SQL> 执行sql
SQL> exec dbms_support.stop_trace
跟踪其他会话:等待事件+绑定变量,相当于level 12的10046事件。
SQL> select sid,serial#,username from v$session where …;
SQL> exec dbms_support.start_trace_in_session(sid=>sid,serial=>serial#,waits=>true,binds=>true);
SQL> exec dbms_support.stop_trace_in_session(sid=>sid,serial=>serial#);

dbms_system包:9i时使用
跟踪其他会话:
SQL> select sid,serial#,username from v$session where …;
SQL> exec dbms_system.set_sql_trace_in_session(sid,serial#,true);
可以等候片刻,跟踪session执行任务,捕获sql操作…
SQL> exec dbms_system.set_sql_trace_in_session(sid,serial#,false);
ps:dbms_system这个包在10gR2官方文档上面没有找到这个包的说明,但数据库中有。
SQL> exec sys.dbms_system.SET_BOOL_PARAM_IN_SESSION(sid, serial#, ‘sql_trace’, TRUE);
SQL> exec sys.dbms_system.SET_BOOL_PARAM_IN_SESSION(sid, serial#, ‘sql_trace’, FALSE);
使用dbms_system.set_ev设置10046事件
SQL> select sid,serial#,username from v$session where …;
SQL> exec dbms_system.set_ev(sid,serial#,10046,12,”);
SQL> exec dbms_system.set_ev(sid,serial#,10046,0,”);
最后一个参数只有为”时,才会生成trace文件,否则不报错,但没有trace文件生成。

3.数据库hang住是一个特定会话出现hang住还是几个会话出现hang住还是所有的会话都出现hang住
如果数据库是一个会话或几个会话出现hang住可以对这个会话执行10046跟踪,可以对这个会话收集一些errorstacks信息,也可以当问题出现时生成一个awr或statspack报告

生成转储和errorstack信息的方法如下:
为了转储跟踪和errorstacks信息,可以使用操作系统进程ID或者oracle进程ID.比如可以通过oracle的sid来查询到操作系统进ID:
SELECT p.pid, p.SPID,s.SID
FROM v$process p, v$session s
WHERE s.paddr = p.addr
AND s.SID = &SID;

SPID是操作系统标识符
SID是oracle会话标识符
PID是oracle进程标识符

比如一个SPID是1254,pid是56如果使用SPID来生成转储和errorstacks信息可以执行下面的语句:
connect / as sysdba
ALTER SESSION SET tracefile_identifier = ‘STACK_10046’;
oradebug setospid 1254
oradebug unlimit

oradebug event 10046 trace name context forever,level 12

oradebug dump errorstack 3
oradebug dump errorstack 3
oradebug dump errorstack 3
oradebug tracefile_name
oradebug event 10046 trace name context off

如果使用PID来生成转储和errorstacks信息可以执行下面的语句:
connect / as sysdba
ALTER SESSION SET tracefile_identifier = ‘STACK_10046’;
oradebug setpid 56
oradebug unlimit

oradebug event 10046 trace name context forever,level 12

oradebug dump errorstack 3
oradebug dump errorstack 3
oradebug dump errorstack 3
oradebug tracefile_name
oradebug event 10046 trace name context off

其中oradebug tracefile_name命令会显示跟踪文件的名字和位置,在生成的跟踪文件名字会包含STACK_10046字符

如果要对当前会话收集errorstacks信息首先要找出当前会话的SPID或PID可以执行如下语句来获得:
SELECT p.pid, p.SPID,s.SID
FROM v$process p, v$session s
WHERE s.paddr = p.addr
AND s.audsid = userenv(‘SESSIONID’) ;

或者

SELECT p.pid, p.SPID,s.SID
FROM v$process p,v$session s
WHERE s.paddr = p.addr
AND s.SID =
(SELECT DISTINCT SID
FROM V$MYSTAT);

如果数据库是所有会话出现hang也就是整个数据库出现hang住了诊断hang住的方法如下:
当一个数据库出现Hang的问题时从数据库中收集信息来诊断挂志的根本原因是非常有用的.数据库Hang的原因往往是孤立的可以使用收集来的诊断信息来解决.另外如果不能解决可以用获得的信息来避免这个问题的再次重现.
解决方法
诊断数据库Hang需要什么信息
数据库Hang的特点是一些进程正在等待另一些进程的完成.通常有一个或多个阻塞进程被困或者正在努力工作但不是迅速的释放资源.为了诊断需要以下信息:
1.Hanganalyze and Systemstate Dumps
2.数据库性能的awr/statspack快照
3.及时的RDA
Hanganalyze and Systemstate Dumps
Hang分析和系统状态转储提供了在一个特定时间点的数据库中的进程信息.Hang分析提供了在Hang链表中所有进程的信息,系统状态提供了数据库中所有进程的信息.当查看一个潜在的Hang情况时你需要判断是否一个进程被因或动行缓慢.通过在两个连续的时间间隔内收集这些转储信息如果进程被困这些跟踪信息可以用于将来的诊断可能帮助你提供一些解决方法.Hang分析用来总结和确认数据库是真的Hang还是只是缓慢并提供了一致性快照,系统状态转储显示了数据库中每一个进程正在做什么
收集Hang分析和系统状态转储信息
登录系统
使用sql*plus以sysdba身份来登录
sqlplus ‘/ as sysdba’
如果连接时出现问题在oracle10gr2中可以使用sqlplus的”preliminary connection’
sqlplus -prelim ‘/ as sysdba’
注意:从oracle 11.2.0.2开始Hang分析在sqlplus的’preliminary connection’连接下将不会生成输出因为它要会请求一个进程的状态对象和一个会话状态对象.如果正试图分析跟踪会输出:
HANG ANALYSIS:
ERROR: Can not perform hang analysis dump without a process state object and a session state object.
( process=(nil), sess=(nil) )
非rac环境收集Hang分析和系统状态的收集命令
有些时候数据库可能只是非常的慢而不是真正的Hang.因此建议收集级别为2的Hang分析和系统状态转储来判断这些进程是正在执行还是已经停止执行
持起分析

sqlplus '/ as sysdba'
oradebug setmypid
oradebug unlimit
oradebug hanganalyze 3
-- Wait one minute before getting the second hanganalyze
oradebug hanganalyze 3
oradebug tracefile_name
exit

系统转储

sqlplus '/ as sysdba'
oradebug setmypid
oradebug unlimit
oradebug dump systemstate 266
oradebug dump systemstate 266
oradebug tracefile_name
exit

rac环境收集Hang分析和系统状态的收集命令
如果在你的系统中没有应用相关的补丁程序使用级别为266或267的系统状态转储会有2个bug.因此在没有应用这些补丁收集这些级别的转储是不明智的选择
补丁信息如下:
Document 11800959.8 Bug 11800959 – A SYSTEMSTATE dump with level >= 10 in RAC dumps huge BUSY GLOBAL CACHE ELEMENTS – can hang/crash instances
Document 11827088.8 Bug 11827088 – Latch ‘gc element’ contention, LMHB terminates the instance
在修正bug 11800959和bug 11827088的情况下对于rac环境惧订Hang分析和系统状态的收集命令如下:

sqlplus '/ as sysdba'
oradebug setorapname reco
oradebug  unlimit
oradebug -g all hanganalyze 3
oradebug -g all hanganalyze 3
oradebug -g all dump systemstate 266
oradebug -g all dump systemstate 266
exit

在没有修正bug 11800959和bug 11827088的情况下对于rac环境惧订Hang分析和系统状态的收集命令如下:

sqlplus '/ as sysdba'
oradebug setorapname reco
oradebug unlimit
oradebug -g all hanganalyze 3
oradebug -g all hanganalyze 3
oradebug -g all dump systemstate 258
oradebug -g all dump systemstate 258
exit

在rac环境中会在每一个实例的跟踪文件中创建所有实例的转储信息
对Hang分析和系统状态转储的级别说明
Hang分析级别
level 3(级别3):在oracle11g之前level 3对Hang链表中的相关进程也会收集一个简短的堆栈信息
系统状态转储级别
level 258(级别258)是一个快速的选择但是会丢失一些锁的元数据信息
level 267(级别267)它包含了理解成本所需要的额外的缓冲区缓存/锁元数据信息
其它的方法
如果不能连接到系统时如何收集系统状态转储信息
通常有两种方法来在系统Hang不能连接时来生成系统状态转储信息
1.alter session set events ‘immediate trace name SYSTEMSTATE level 10’;
2.$ sqlplus
connect sys/passwd as sysdba
oradebug setospid oradebug unlimit
oradebug dump systemstate 10
(注意:在oradebug中不能使用任何半冒号,如果你的数据库是比oracle9i还老的版本你将需要使用svrmgrl来连接到内部)
当你使用这两种方法中的一种时,要确保在两次转储时内部连接断开.这种方法生成的转储将在你的user_dump_dest目录中是分开的ora_.trc文件
在非常严重的情况下不能使用svrmgrl或sqlplus进行连接执行这些必要的命令.在这种情况下仍然有一个后门方法使用调试器比如你的系统有dbx的话可以用dbx来生成系统状态转储信息.被连接到的转储核心进程可能会被杀死所以不能连接到一个oracle后台进程.dbx的语法如下:
dbx -a PID (where PID = any oracle shadow process)
dbx() print ksudss(10)
…return value printed here
dbx() detach
首先你需要找到一个影子进程

(jy) % ps -ef |grep sqlplus
osupport  78526 154096   0 12:11:05  pts/1  0:00 sqlplus scott/tiger
osupport  94130  84332   1 12:11:20  pts/3  0:00 grep sqlplus
(jy) % ps -ef |grep 78526
osupport  28348  78526   0 12:11:05      -  0:00 oracles734 (DESCRIPTION=(LOCAL
osupport  78526 154096   0 12:11:05  pts/1  0:00 sqlplus scott/tiger
osupport  94132  84332   1 12:11:38  pts/3  0:00 grep 78526

这样将会连接到影子进程PID 28348上.当返回提示符时输入ksudss(10)命令和detach:

(jy) % dbx -a 28348
Waiting to attach to process 28348 ...
Successfully attached to oracle.
warning: Directory containing oracle could not be determined.
Apply 'use' command to initialize source path.

Type 'help' for help.
reading symbolic information ...
stopped in read at 0xd016fdf0
0xd016fdf0 (read+0x114) 80410014        lwz   r2,0x14(r1)
(dbx) print ksudss(10)
2
(dbx) detach

在user_dump_dest目录中你将会通过跟踪的pid找到一个系统状态转储文件

(jy) % ls -lrt *28348*
-rw-r-----   1 osupport dba        46922 Oct 10 12:12 ora_28348.trc

core_28348:
total 72
-rw-r--r--   1 osupport dba        16567 Oct 10 12:12 core
drwxr-xr-x   7 osupport dba        12288 Oct 10 12:12 ../
drwxr-x---   2 osupport dba          512 Oct 10 12:12 ./

在跟踪文件中你将会找到常用的头信息.在oracle7.3.4并行操作系统中在这后面紧跟的是锁信息然后才是系统转储信息.
在oracle8并行操作系统中和非并行操作系统和oracle7.3.4非并行操作系统的系统状态信息是紧跟头信息.
在转储文件中头信息如下:

Dump file /oracle/mpp/734/rdbms/log/ora_28348.trc
Oracle7 Server Release 7.3.4.4.1 - Production
With the distributed, replication, parallel query, Parallel Server
and Spatial Data options
PL/SQL Release 2.3.4.4.1 - Production
ORACLE_HOME = /oracle/mpp/734
System name:    AIX
Node name:      saki
Release:        3
Version:        4
Machine:        000089914C00
Instance name: s734
Redo thread mounted by this instance: 2
Oracle process number: 0
Unix process pid: 28348, image:

ksinfy: nfytype = 0x5
ksinfy: calling scggra(&se)
scggra: SCG_PROCESS_LOCKING not defined
scggra: calling lk_group_attach()
ksinfy: returning
*** SESSION ID:(12.15) 2000.10.10.12.11.06.000
ksqcmi: get or convert
ksqcmi: get or convert
*** 2000.10.10.12.12.08.000
===================================================
SYSTEM STATE

.....

确保在这个文件中有一个end of system state.可以对它使用grep或在vi中搜索.如果没有那么这个跟踪文件是不过完整.
可能是因为init.ora文件中的max_dump_file的大小太小了.
对于oracle10g及以后的版本:
在有些情况下不连接到实例是允许的(在有些ora-20的情况下,对于oracle10.1.x,对于sqlplus有一个新选项来允许访问实例来生成跟踪文件)sqlplus -prelim / as sysdba
例如

export ORACLE_SID=PROD                                 ## Replace PROD with the SID you want to trace
sqlplus -prelim / as sysdba
oradebug setmypid
oradebug unlimit;
oradebug dump systemstate 10

在rac系统中,Hang分析,系统转储和其它一些rac信息可以使用racdiag.sql脚本来进行收集:

-- NAME: RACDIAG.SQL
-- SYS OR INTERNAL USER, CATPARR.SQL ALREADY RUN, PARALLEL QUERY OPTION ON
-- ------------------------------------------------------------------------
-- AUTHOR:
-- Michael Polaski - Oracle Support Services
-- Copyright 2002, Oracle Corporation
-- ------------------------------------------------------------------------
-- PURPOSE:
-- This script is intended to provide a user friendly guide to troubleshoot
-- RAC hung sessions or slow performance scenerios. The script includes
-- information to gather a variety of important debug information to determine
-- the cause of a RAC session level hang. The script will create a file
-- called racdiag_.out in your local directory while dumping hang analyze
-- dumps in the user_dump_dest(s) and background_dump_dest(s) on all nodes.
--
-- ------------------------------------------------------------------------
-- DISCLAIMER:
-- This script is provided for educational purposes only. It is NOT
-- supported by Oracle World Wide Technical Support.
-- The script has been tested and appears to work as intended.
-- You should always run new scripts on a test instance initially.
-- ------------------------------------------------------------------------
-- Script output is as follows:

set echo off
set feedback off
column timecol new_value timestamp
column spool_extension new_value suffix
select to_char(sysdate,'Mondd_hhmi') timecol,
'.out' spool_extension from sys.dual;
column output new_value dbname
select value || '_' output
from v$parameter where name = 'db_name';
spool racdiag_&&dbname&×tamp&&suffix
set lines 200
set pagesize 35
set trim on
set trims on
alter session set nls_date_format = 'MON-DD-YYYY HH24:MI:SS';
alter session set timed_statistics = true;
set feedback on
select to_char(sysdate) time from dual;

set numwidth 5
column host_name format a20 tru
select inst_id, instance_name, host_name, version, status, startup_time
from gv$instance
order by inst_id;

set echo on

-- WAIT CHAINS
-- 11.x+ Only (This will not work in < v11
-- See Note 1428210.1 for instructions on interpreting.
set pages 1000
set lines 120
set heading off
column w_proc format a50 tru
column instance format a20 tru
column inst format a28 tru
column wait_event format a50 tru
column p1 format a16 tru
column p2 format a16 tru
column p3 format a15 tru
column Seconds format a50 tru
column sincelw format a50 tru
column blocker_proc format a50 tru
column waiters format a50 tru
column chain_signature format a100 wra
column blocker_chain format a100 wra
SELECT *
FROM (SELECT 'Current Process: '||osid W_PROC, 'SID '||i.instance_name INSTANCE,
'INST #: '||instance INST,'Blocking Process: '||decode(blocker_osid,null,'',blocker_osid)||
' from Instance '||blocker_instance BLOCKER_PROC,'Number of waiters: '||num_waiters waiters,
'Wait Event: ' ||wait_event_text wait_event, 'P1: '||p1 p1, 'P2: '||p2 p2, 'P3: '||p3 p3,
'Seconds in Wait: '||in_wait_secs Seconds, 'Seconds Since Last Wait: '||time_since_last_wait_secs sincelw,
'Wait Chain: '||chain_id ||': '||chain_signature chain_signature,'Blocking Wait Chain: '||decode(blocker_chain_id,null,
'',blocker_chain_id) blocker_chain
FROM v$wait_chains wc,
v$instance i
WHERE wc.instance = i.instance_number (+)
AND ( num_waiters > 0
OR ( blocker_osid IS NOT NULL
AND in_wait_secs > 10 ) )
ORDER BY chain_id,
num_waiters DESC)
WHERE ROWNUM < 101; -- Taking Hang Analyze dumps  -- This may take a little while...  oradebug setmypid  oradebug unlimit  oradebug -g all hanganalyze 3  -- This part may take the longest, you can monitor bdump or udump to see if  -- the file is being generated.  oradebug -g all dump systemstate 258  -- WAITING SESSIONS:  -- The entries that are shown at the top are the sessions that have  -- waited the longest amount of time that are waiting for non-idle wait  -- events (event column). You can research and find out what the wait  -- event indicates (along with its parameters) by checking the Oracle  -- Server Reference Manual or look for any known issues or documentation  -- by searching Metalink for the event name in the search bar. Example  -- (include single quotes): [ 'buffer busy due to global cache' ].  -- Metalink and/or the Server Reference Manual should return some useful  -- information on each type of wait event. The inst_id column shows the  -- instance where the session resides and the SID is the unique identifier  -- for the session (gv$session). The p1, p2, and p3 columns will show  -- event specific information that may be important to debug the problem.  -- To find out what the p1, p2, and p3 indicates see the next section.  -- Items with wait_time of anything other than 0 indicate we do not know  -- how long these sessions have been waiting.  --  set numwidth 15 set heading on column state format a7 tru  column event format a25 tru  column last_sql format a40 tru  select sw.inst_id, sw.sid, sw.state, sw.event, sw.seconds_in_wait seconds,  sw.p1, sw.p2, sw.p3, sa.sql_text last_sql  from gv$session_wait sw, gv$session s, gv$sqlarea sa  where sw.event not in  ('rdbms ipc message','smon timer','pmon timer',  'SQL*Net message from client','lock manager wait for remote message',  'ges remote message', 'gcs remote message', 'gcs for action', 'client message',  'pipe get', 'null event', 'PX Idle Wait', 'single-task message',  'PX Deq: Execution Msg', 'KXFQ: kxfqdeq - normal deqeue',  'listen endpoint status','slave wait','wakeup time manager')  and sw.seconds_in_wait > 0
and (sw.inst_id = s.inst_id and sw.sid = s.sid)
and (s.inst_id = sa.inst_id and s.sql_address = sa.address)
order by seconds desc;

-- EVENT PARAMETER LOOKUP:
-- This section will give a description of the parameter names of the
-- events seen in the last section. p1test is the parameter value for
-- p1 in the WAITING SESSIONS section while p2text is the parameter
-- value for p3 and p3 text is the parameter value for p3. The
-- parameter values in the first section can be helpful for debugging
-- the wait event.
--
column event format a30 tru
column p1text format a25 tru
column p2text format a25 tru
column p3text format a25 tru
select distinct event, p1text, p2text, p3text
from gv$session_wait sw
where sw.event not in ('rdbms ipc message','smon timer','pmon timer',
'SQL*Net message from client','lock manager wait for remote message',
'ges remote message', 'gcs remote message', 'gcs for action', 'client message',
'pipe get', 'null event', 'PX Idle Wait', 'single-task message',
'PX Deq: Execution Msg', 'KXFQ: kxfqdeq - normal deqeue',
'listen endpoint status','slave wait','wakeup time manager')
and seconds_in_wait > 0
order by event;

-- GES LOCK BLOCKERS:
-- This section will show us any sessions that are holding locks that
-- are blocking other users. The inst_id will show us the instance that
-- the session resides on while the sid will be a unique identifier for
-- the session. The grant_level will show us how the GES lock is granted to
-- the user. The request_level will show us what status we are trying to
-- obtain.  The lockstate column will show us what status the lock is in.
-- The last column shows how long this session has been waiting.
--
set numwidth 5
column state format a16 tru;
column event format a30 tru;
select dl.inst_id, s.sid, p.spid, dl.resource_name1,
decode(substr(dl.grant_level,1,8),'KJUSERNL','Null','KJUSERCR','Row-S (SS)',
'KJUSERCW','Row-X (SX)','KJUSERPR','Share','KJUSERPW','S/Row-X (SSX)',
'KJUSEREX','Exclusive',request_level) as grant_level,
decode(substr(dl.request_level,1,8),'KJUSERNL','Null','KJUSERCR','Row-S (SS)',
'KJUSERCW','Row-X (SX)','KJUSERPR','Share','KJUSERPW','S/Row-X (SSX)',
'KJUSEREX','Exclusive',request_level) as request_level,
decode(substr(dl.state,1,8),'KJUSERGR','Granted','KJUSEROP','Opening',
'KJUSERCA','Canceling','KJUSERCV','Converting') as state,
s.sid, sw.event, sw.seconds_in_wait sec
from gv$ges_enqueue dl, gv$process p, gv$session s, gv$session_wait sw
where blocker = 1
and (dl.inst_id = p.inst_id and dl.pid = p.spid)
and (p.inst_id = s.inst_id and p.addr = s.paddr)
and (s.inst_id = sw.inst_id and s.sid = sw.sid)
order by sw.seconds_in_wait desc;

-- GES LOCK WAITERS:
-- This section will show us any sessions that are waiting for locks that
-- are blocked by other users. The inst_id will show us the instance that
-- the session resides on while the sid will be a unique identifier for
-- the session. The grant_level will show us how the GES lock is granted to
-- the user. The request_level will show us what status we are trying to
-- obtain.  The lockstate column will show us what status the lock is in.
-- The last column shows how long this session has been waiting.
--
set numwidth 5
column state format a16 tru;
column event format a30 tru;
select dl.inst_id, s.sid, p.spid, dl.resource_name1,
decode(substr(dl.grant_level,1,8),'KJUSERNL','Null','KJUSERCR','Row-S (SS)',
'KJUSERCW','Row-X (SX)','KJUSERPR','Share','KJUSERPW','S/Row-X (SSX)',
'KJUSEREX','Exclusive',request_level) as grant_level,
decode(substr(dl.request_level,1,8),'KJUSERNL','Null','KJUSERCR','Row-S (SS)',
'KJUSERCW','Row-X (SX)','KJUSERPR','Share','KJUSERPW','S/Row-X (SSX)',
'KJUSEREX','Exclusive',request_level) as request_level,
decode(substr(dl.state,1,8),'KJUSERGR','Granted','KJUSEROP','Opening',
'KJUSERCA','Cancelling','KJUSERCV','Converting') as state,
s.sid, sw.event, sw.seconds_in_wait sec
from gv$ges_enqueue dl, gv$process p, gv$session s, gv$session_wait sw
where blocked = 1
and (dl.inst_id = p.inst_id and dl.pid = p.spid)
and (p.inst_id = s.inst_id and p.addr = s.paddr)
and (s.inst_id = sw.inst_id and s.sid = sw.sid)
order by sw.seconds_in_wait desc;

-- LOCAL ENQUEUES:
-- This section will show us if there are any local enqueues. The inst_id will
-- show us the instance that the session resides on while the sid will be a
-- unique identifier for. The addr column will show the lock address. The type
-- will show the lock type. The id1 and id2 columns will show specific
-- parameters for the lock type.
--
set numwidth 12
column event format a12 tru
select l.inst_id, l.sid, l.addr, l.type, l.id1, l.id2,
decode(l.block,0,'blocked',1,'blocking',2,'global') block,
sw.event, sw.seconds_in_wait sec
from gv$lock l, gv$session_wait sw
where (l.sid = sw.sid and l.inst_id = sw.inst_id)
and l.block in (0,1)
order by l.type, l.inst_id, l.sid;

-- LATCH HOLDERS:
-- If there is latch contention or 'latch free' wait events in the WAITING
-- SESSIONS section we will need to find out which proceseses are holding
-- latches. The inst_id will show us the instance that the session resides
-- on while the sid will be a unique identifier for. The username column
-- will show the session's username. The os_user column will show the os
-- user that the user logged in as. The name column will show us the type
-- of latch being waited on. You can search Metalink for the latch name in
-- the search bar. Example (include single quotes):
-- [ 'library cache' latch ]. Metalink should return some useful information
-- on the type of latch.
--
set numwidth 5
select distinct lh.inst_id, s.sid, s.username, p.username os_user, lh.name
from gv$latchholder lh, gv$session s, gv$process p
where (lh.sid = s.sid and lh.inst_id = s.inst_id)
and (s.inst_id = p.inst_id and s.paddr = p.addr)
order by lh.inst_id, s.sid;

-- LATCH STATS:
-- This view will show us latches with less than optimal hit ratios
-- The inst_id will show us the instance for the particular latch. The
-- latch_name column will show us the type of latch. You can search Metalink
-- for the latch name in the search bar. Example (include single quotes):
-- [ 'library cache' latch ]. Metalink should return some useful information
-- on the type of latch. The hit_ratio shows the percentage of time we
-- successfully acquired the latch.
--
column latch_name format a30 tru
select inst_id, name latch_name,
round((gets-misses)/decode(gets,0,1,gets),3) hit_ratio,
round(sleeps/decode(misses,0,1,misses),3) "SLEEPS/MISS"
from gv$latch
where round((gets-misses)/decode(gets,0,1,gets),3) < .99
and gets != 0
order by round((gets-misses)/decode(gets,0,1,gets),3);

-- No Wait Latches:
--
select inst_id, name latch_name,
round((immediate_gets/(immediate_gets+immediate_misses)), 3) hit_ratio,
round(sleeps/decode(immediate_misses,0,1,immediate_misses),3) "SLEEPS/MISS"
from gv$latch
where round((immediate_gets/(immediate_gets+immediate_misses)), 3) < .99  and immediate_gets + immediate_misses > 0
order by round((immediate_gets/(immediate_gets+immediate_misses)), 3);

-- GLOBAL CACHE CR PERFORMANCE
-- This shows the average latency of a consistent block request.
-- AVG CR BLOCK RECEIVE TIME should typically be about 15 milliseconds
-- depending on your system configuration and volume, is the average
-- latency of a consistent-read request round-trip from the requesting
-- instance to the holding instance and back to the requesting instance. If
-- your CPU has limited idle time and your system typically processes
-- long-running queries, then the latency may be higher. However, it is
-- possible to have an average latency of less than one millisecond with
-- User-mode IPC. Latency can be influenced by a high value for the
-- DB_MULTI_BLOCK_READ_COUNT parameter. This is because a requesting process
-- can issue more than one request for a block depending on the setting of
-- this parameter. Correspondingly, the requesting process may wait longer.
-- Also check interconnect badwidth, OS tcp settings, and OS udp settings if
-- AVG CR BLOCK RECEIVE TIME is high.
--
set numwidth 20
column "AVG CR BLOCK RECEIVE TIME (ms)" format 9999999.9
select b1.inst_id, b2.value "GCS CR BLOCKS RECEIVED",
b1.value "GCS CR BLOCK RECEIVE TIME",
((b1.value / b2.value) * 10) "AVG CR BLOCK RECEIVE TIME (ms)"
from gv$sysstat b1, gv$sysstat b2
where b1.name = 'global cache cr block receive time' and
b2.name = 'global cache cr blocks received' and b1.inst_id = b2.inst_id
or b1.name = 'gc cr block receive time' and
b2.name = 'gc cr blocks received' and b1.inst_id = b2.inst_id ;

-- GLOBAL CACHE LOCK PERFORMANCE
-- This shows the average global enqueue get time.
-- Typically AVG GLOBAL LOCK GET TIME should be 20-30 milliseconds. the
-- elapsed time for a get includes the allocation and initialization of a
-- new global enqueue. If the average global enqueue get (global cache
-- get time) or average global enqueue conversion times are excessive,
-- then your system may be experiencing timeouts. See the 'WAITING SESSIONS',
-- 'GES LOCK BLOCKERS', GES LOCK WAITERS', and 'TOP 10 WAIT EVENTS ON SYSTEM'
-- sections if the AVG GLOBAL LOCK GET TIME is high.
--
set numwidth 20
column "AVG GLOBAL LOCK GET TIME (ms)" format 9999999.9
select b1.inst_id, (b1.value + b2.value) "GLOBAL LOCK GETS",
b3.value "GLOBAL LOCK GET TIME",
(b3.value / (b1.value + b2.value) * 10) "AVG GLOBAL LOCK GET TIME (ms)"
from gv$sysstat b1, gv$sysstat b2, gv$sysstat b3
where b1.name = 'global lock sync gets' and
b2.name = 'global lock async gets' and b3.name = 'global lock get time'
and b1.inst_id = b2.inst_id and b2.inst_id = b3.inst_id
or b1.name = 'global enqueue gets sync' and
b2.name = 'global enqueue gets async' and b3.name = 'global enqueue get time'
and b1.inst_id = b2.inst_id and b2.inst_id = b3.inst_id;

-- RESOURCE USAGE
-- This section will show how much of our resources we have used.
--
set numwidth 8
select inst_id, resource_name, current_utilization, max_utilization,
initial_allocation
from gv$resource_limit
where max_utilization > 0
order by inst_id, resource_name;

-- DLM TRAFFIC INFORMATION
-- This section shows how many tickets are available in the DLM. If the
-- TCKT_WAIT columns says "YES" then we have run out of DLM tickets which
-- could cause a DLM hang. Make sure that you also have enough TCKT_AVAIL.
--
set numwidth 10
select * from gv$dlm_traffic_controller
order by TCKT_AVAIL;

-- DLM MISC
--
set numwidth 10
select * from gv$dlm_misc;

-- LOCK CONVERSION DETAIL:
-- This view shows the types of lock conversion being done on each instance.
--
select * from gv$lock_activity;

-- INITIALIZATION PARAMETERS:
-- Non-default init parameters for each node.
--
set numwidth 5
column name format a30 tru
column value format a50 wra
column description format a60 tru
select inst_id, name, value, description
from gv$parameter
where isdefault = 'FALSE'
order by inst_id, name;

-- TOP 10 WAIT EVENTS ON SYSTEM
-- This view will provide a summary of the top wait events in the db.
--
set numwidth 10
column event format a25 tru
select inst_id, event, time_waited, total_waits, total_timeouts
from (select inst_id, event, time_waited, total_waits, total_timeouts
from gv$system_event where event not in ('rdbms ipc message','smon timer',
'pmon timer', 'SQL*Net message from client','lock manager wait for remote message',
'ges remote message', 'gcs remote message', 'gcs for action', 'client message',
'pipe get', 'null event', 'PX Idle Wait', 'single-task message',
'PX Deq: Execution Msg', 'KXFQ: kxfqdeq - normal deqeue',
'listen endpoint status','slave wait','wakeup time manager')
order by time_waited desc)
where rownum < 11  order by time_waited desc;  -- SESSION/PROCESS REFERENCE:  -- This section is very important for most of the above sections to find out  -- which user/os_user/process is identified to which session/process.  --  set numwidth 7  column event format a30 tru  column program format a25 tru  column username format a15 tru  select p.inst_id, s.sid, s.serial#, p.pid, p.spid, p.program, s.username,  p.username os_user, sw.event, sw.seconds_in_wait sec  from gv$process p, gv$session s, gv$session_wait sw  where (p.inst_id = s.inst_id and p.addr = s.paddr)  and (s.inst_id = sw.inst_id and s.sid = sw.sid)  order by p.inst_id, s.sid;  -- SYSTEM STATISTICS:  -- All System Stats with values of > 0. These can be referenced in the
-- Server Reference Manual
--
set numwidth 5
column name format a60 tru
column value format 9999999999999999999999999
select inst_id, name, value
from gv$sysstat
where value > 0
order by inst_id, name;

-- CURRENT SQL FOR WAITING SESSIONS:
-- Current SQL for any session in the WAITING SESSIONS list
--
set numwidth 5
column sql format a80 wra
select sw.inst_id, sw.sid, sw.seconds_in_wait sec, sa.sql_text sql
from gv$session_wait sw, gv$session s, gv$sqlarea sa
where sw.sid = s.sid (+)
and sw.inst_id = s.inst_id (+)
and s.sql_address = sa.address
and sw.event not in ('rdbms ipc message','smon timer','pmon timer',
'SQL*Net message from client','lock manager wait for remote message',
'ges remote message', 'gcs remote message', 'gcs for action', 'client message',
'pipe get', 'null event', 'PX Idle Wait', 'single-task message',
'PX Deq: Execution Msg', 'KXFQ: kxfqdeq - normal deqeue',
'listen endpoint status','slave wait','wakeup time manager')
and sw.seconds_in_wait > 0
order by sw.seconds_in_wait desc;

-- WAIT CHAINS
-- 11.x+ Only (This will not work in < v11
-- See Note 1428210.1 for instructions on interpreting.
set pages 1000
set lines 120
set heading off
column w_proc format a50 tru
column instance format a20 tru
column inst format a28 tru
column wait_event format a50 tru
column p1 format a16 tru
column p2 format a16 tru
column p3 format a15 tru
column seconds format a50 tru
column sincelw format a50 tru
column blocker_proc format a50 tru
column waiters format a50 tru
column chain_signature format a100 wra
column blocker_chain format a100 wra
SELECT *
FROM (SELECT 'Current Process: '||osid W_PROC, 'SID '||i.instance_name INSTANCE,
'INST #: '||instance INST,'Blocking Process: '||decode(blocker_osid,null,'',blocker_osid)||
' from Instance '||blocker_instance BLOCKER_PROC,'Number of waiters: '||num_waiters waiters,
'Wait Event: ' ||wait_event_text wait_event, 'P1: '||p1 p1, 'P2: '||p2 p2, 'P3: '||p3 p3,
'Seconds in Wait: '||in_wait_secs Seconds, 'Seconds Since Last Wait: '||time_since_last_wait_secs sincelw,
'Wait Chain: '||chain_id ||': '||chain_signature chain_signature,'Blocking Wait Chain: '||decode(blocker_chain_id,null,
'',blocker_chain_id) blocker_chain
FROM v$wait_chains wc,
v$instance i
WHERE wc.instance = i.instance_number (+)
AND ( num_waiters > 0
OR ( blocker_osid IS NOT NULL
AND in_wait_secs > 10 ) )
ORDER BY chain_id,
num_waiters DESC)
WHERE ROWNUM < 101;

-- Taking Hang Analyze dumps
-- This may take a little while...
oradebug setmypid
oradebug unlimit
oradebug -g all hanganalyze 3
-- This part may take the longest, you can monitor bdump or udump to see
-- if the file is being generated.
oradebug -g all dump systemstate 258

set echo off

select to_char(sysdate) time from dual;

spool off

-- ---------------------------------------------------------------------------
Prompt;
Prompt racdiag output files have been written to:;
Prompt;
host pwd
Prompt alert log and trace files are located in:;
column host_name format a12 tru
column name format a20 tru
column value format a60 tru
select distinct i.host_name, p.name, p.value
from gv$instance i, gv$parameter p
where p.inst_id = i.inst_id (+)
and p.name like '%_dump_dest'
and p.name != 'core_dump_dest';
v$wait_chains

从oracle11gr1开始,dia0后台进程开始收集Hang分析信息并存储在内存中的"hang analysis cache"中.它会每3秒钟收集一次本地的Hang分析和第10秒钟收集一次全局(rac)Hang分析信息.这些信息在出现Hang时提供快速查看Hang链表的方法.
存储在"hang analysiz cache"中的数据对于诊断数据库竞争和Hang是非常有效的
有许多数据库功能可以利用Hang分析缓存:Hang Management, Resource Manager Idle Blocker Kill,
SQL Tune Hang Avoidance和PMON清除以及外部工具象Procwatcher
下面是oracle11gr2中v$wait_chains视图的描述:

SQL> desc v$wait_chains
  Name                                      Null     Type
  ----------------------------------------- -------- ----------------------
  CHAIN_ID                                           NUMBER
  CHAIN_IS_CYCLE                                     VARCHAR2(5)
  CHAIN_SIGNATURE                                    VARCHAR2(801)
  CHAIN_SIGNATURE_HASH                               NUMBER
  INSTANCE                                           NUMBER
  OSID                                               VARCHAR2(25)
  PID                                                NUMBER
  SID                                                NUMBER
  SESS_SERIAL#                                       NUMBER
  BLOCKER_IS_VALID                                   VARCHAR2(5)
  BLOCKER_INSTANCE                                   NUMBER
  BLOCKER_OSID                                       VARCHAR2(25)
  BLOCKER_PID                                        NUMBER
  BLOCKER_SID                                        NUMBER
  BLOCKER_SESS_SERIAL#                               NUMBER
  BLOCKER_CHAIN_ID                                   NUMBER
  IN_WAIT                                            VARCHAR2(5)
  TIME_SINCE_LAST_WAIT_SECS                          NUMBER
  WAIT_ID                                            NUMBER
  WAIT_EVENT                                         NUMBER
  WAIT_EVENT_TEXT                                    VARCHAR2(64)
  P1                                                 NUMBER
  P1_TEXT                                            VARCHAR2(64)
  P2                                                 NUMBER
  P2_TEXT                                            VARCHAR2(64)
  P3                                                 NUMBER
  P3_TEXT                                            VARCHAR2(64)
  IN_WAIT_SECS                                       NUMBER
  TIME_REMAINING_SECS                                NUMBER
  NUM_WAITERS                                        NUMBER
  ROW_WAIT_OBJ#                                      NUMBER
  ROW_WAIT_FILE#                                     NUMBER
  ROW_WAIT_BLOCK#                                    NUMBER
  ROW_WAIT_ROW#                                      NUMBER

注意:v$wait_chains等价于gv$视图可能在rac环境中报告多个实例
使用sql来查询基本信息

SQL> SELECT chain_id, num_waiters, in_wait_secs, osid, blocker_osid, substr(wait_event_text,1,30)
 FROM v$wait_chains; 2

 CHAIN_ID   NUM_WAITERS IN_WAIT_SECS OSID           BLOCKER_OSID         SUBSTR(WAIT_EVENT_TEXT,1,30)
 ---------- ----------- ------------ -------------- ------------------------- -----------------------------
1          0           10198        21045          21044                      enq: TX - row lock contention
 1          1           10214        21044                                    SQL*Net message from client

查询top 100 wait chain processs

 set pages 1000
 set lines 120
 set heading off
 column w_proc format a50 tru
 column instance format a20 tru
 column inst format a28 tru
 column wait_event format a50 tru
 column p1 format a16 tru
 column p2 format a16 tru
 column p3 format a15 tru
 column Seconds format a50 tru
 column sincelw format a50 tru
 column blocker_proc format a50 tru
 column waiters format a50 tru
 column chain_signature format a100 wra
 column blocker_chain format a100 wra

 SELECT *
 FROM (SELECT 'Current Process: '||osid W_PROC, 'SID '||i.instance_name INSTANCE,
 'INST #: '||instance INST,'Blocking Process: '||decode(blocker_osid,null,'',blocker_osid)||
 ' from Instance '||blocker_instance BLOCKER_PROC,'Number of waiters: '||num_waiters waiters,
 'Wait Event: ' ||wait_event_text wait_event, 'P1: '||p1 p1, 'P2: '||p2 p2, 'P3: '||p3 p3,
 'Seconds in Wait: '||in_wait_secs Seconds, 'Seconds Since Last Wait: '||time_since_last_wait_secs sincelw,
 'Wait Chain: '||chain_id ||': '||chain_signature chain_signature,'Blocking Wait Chain: '||decode(blocker_chain_id,null,
 '',blocker_chain_id) blocker_chain
 FROM v$wait_chains wc,
 v$instance i
 WHERE wc.instance = i.instance_number (+)
 AND ( num_waiters > 0
 OR ( blocker_osid IS NOT NULL
 AND in_wait_secs > 10 ) )
 ORDER BY chain_id,
 num_waiters DESC)
 WHERE ROWNUM < 101;


Current Process:21549                                   SID RAC1                 INST #: 1
Blocking Process: from Instance                   Number of waiters:1
Wait Event:SQL*Net message from client                  P1: 1650815232  P2: 1     P3:0
Seconds in Wait:36                                      Seconds Since Last Wait:
Wait Chaing:1 : 'SQL*Net message from client '< ='enq: TX - row lock contention'
Blocking Wait Chain:

Current Process:25627                                   SID RAC1                 INST #: 1
Blocking Process:21549 from Instance 1                  Number of waiters:0
Wait Event:enq: TX - row lock contention                P1:1415053318 P2: 524316 P3:50784
Seconds in Wait:22                                      Seconds Since Last Wait:
Wait Chain:1 : 'SQL*Net message from client '< ='enq: TX - row lock contention'
Blocking Wait Chain:

ospid 25627正等待一个TX lock正被ospid 21549所阻塞
ospid 21549正空闲等待'SQL*Net message from client'
在oracle11gr2中的最终阻塞会话
在oracle11gr2中可能将v$session.final_blocking_session看作是最终的阻塞者.最终的阻会话/进程在top等待链表上.
这些会话/进程可能是造成问题的原因.

set pages 1000
set lines 120
set heading off
column w_proc format a50 tru
column instance format a20 tru
column inst format a28 tru
column wait_event format a50 tru
column p1 format a16 tru
column p2 format a16 tru
column p3 format a15 tru
column Seconds format a50 tru
column sincelw format a50 tru
column blocker_proc format a50 tru
column fblocker_proc format a50 tru
column waiters format a50 tru
column chain_signature format a100 wra
column blocker_chain format a100 wra

SELECT *
FROM (SELECT 'Current Process: '||osid W_PROC, 'SID '||i.instance_name INSTANCE,
 'INST #: '||instance INST,'Blocking Process: '||decode(blocker_osid,null,'',blocker_osid)||
 ' from Instance '||blocker_instance BLOCKER_PROC,
 'Number of waiters: '||num_waiters waiters,
 'Final Blocking Process: '||decode(p.spid,null,'',
 p.spid)||' from Instance '||s.final_blocking_instance FBLOCKER_PROC,
 'Program: '||p.program image,
 'Wait Event: ' ||wait_event_text wait_event, 'P1: '||wc.p1 p1, 'P2: '||wc.p2 p2, 'P3: '||wc.p3 p3,
 'Seconds in Wait: '||in_wait_secs Seconds, 'Seconds Since Last Wait: '||time_since_last_wait_secs sincelw,
 'Wait Chain: '||chain_id ||': '||chain_signature chain_signature,'Blocking Wait Chain: '||decode(blocker_chain_id,null,
 '',blocker_chain_id) blocker_chain
FROM v$wait_chains wc,
 gv$session s,
 gv$session bs,
 gv$instance i,
 gv$process p
WHERE wc.instance = i.instance_number (+)
 AND (wc.instance = s.inst_id (+) and wc.sid = s.sid (+)
 and wc.sess_serial# = s.serial# (+))
 AND (s.final_blocking_instance = bs.inst_id (+) and s.final_blocking_session = bs.sid (+))
 AND (bs.inst_id = p.inst_id (+) and bs.paddr = p.addr (+))
 AND ( num_waiters > 0
 OR ( blocker_osid IS NOT NULL
 AND in_wait_secs > 10 ) )
ORDER BY chain_id,
 num_waiters DESC)
WHERE ROWNUM < 101;



Current Process:2309                                    SID RAC1                 INST #: 1
Blocking Process: from Instance                   Number of waiters:2
Wait Event:SQL*Net message from client                  P1: 1650815232  P2: 1     P3:0
Seconds in Wait:157                                     Seconds Since Last Wait:
Wait Chaing:1 : 'SQL*Net message from client '< ='enq: TM - contention'<='enq: TM - contention'
Blocking Wait Chain:

Current Process:2395                                    SID RAC1                 INST #: 1
Blocking Process:2309 from Instance 1                   Number of waiters:0
Final Block Process:2309 from Instance 1                Program: oracle@racdbe1.us.oracle.com (TNS V1-V3)
Wait Event:enq: TX - contention                         P1:1415053318 P2: 524316 P3:50784
Seconds in Wait:139                                      Seconds Since Last Wait:
Wait Chain:1 : 'SQL*Net message from client '< ='enq: TM - contention'<='enq: TM - contention'
Blocking Wait Chain:

B.对数据库性能生成一个awr/statspack快照
C.收集最新的RDA
最新的RDA提供了大量额外关于数据库配置和性能度量的信息可以用来检测可能影响性能的热点的后台进程问题
有时数据库不是真正的被hang住可是只是'spinning' cpu.可以使用以下方法来检查服务器是hang还是spin如果一个操作执行的时间比期待的时间长或者这个操作损害了其它操作的性能时那么最好是检查v$session_wait视图.这个视图显示了在系统中会话当前正在等待的信息.可以使用下面的脚本来操作.

column sid format 990
column seq# format 99990
column wait_time heading 'WTime' format 99990
column event format a30
column p1 format 9999999990
column p2 format 9999999990
column p3 format 9990
select sid,event,seq#,p1,p2,p3,wait_time
from V$session_wait
order by sid
/

上面的查询最少应该执行三次并比较其它查询结果
列意思
sid– 会话的系统标识符
seq#–序列号.当一个特定会话的等待一个新的事件时这个数字会增加.它能告诉你一个会话是否正在执行
evnet–会话正在等待的或最后等待的操作
p1,p2,p3–它们代表不同的等待值
wait_time–0指示这个会话正在等待的事件.非0指示这个会话最后等待的事件和会话正使用CPU
例如:

 SID EVENT                            SEQ#          P1          P2    P3  WTime
---- ------------------------------ ------ ----------- ----------- ----- ------
   1 pmon timer                        335         300           0     0      0
   2 rdbms ipc message                 779         300           0     0      0
   6 smon timer                         74         300           0     0      0
   9 Null event                        347           0         300     0      0
  16 SQL*Net message from client      1064  1650815315           1     0     -1

如果脚本查询的结果显示正在等待一个enqueue等待事件那么你将需要检查与你hang会话相关的锁信息
column sid format 990
column type format a2
column id1 format 9999999990
column id2 format 9999999990
column Lmode format 990
column request format 990
select * from v$lock
/
Spinning
在spin的情况下事件通常来说是静态的且会话不会是正在等待一个事件–而是在等待cpu(注意在极少数情况下,这个事件依赖于执行spin的代码也可能不会静态的.如果会自豪感是spin它将严重使用cpu和内存资源.
对于一个spin的情况重要的是要检测会话正处于spinning的代码.从事件的一些迹象说明通常需要对一个进程生成几次的错误堆栈信息用来分析:
connect sys/sys as sysdba
oradebug setospid
oradebug unlimit
oradebug dump errorstack 3
oradebug dump errorstack 3
oradebug dump errorstack 3
这里的spid是操作系统标识符可以从v$process视图是得到.
Hanging
在正常的情况下在v$session_wait视图中的值应该是用每个会话执行的不同操作来替换.
在hang住的情况下对于一个或一组特定会话的所有系统事件将会是保持静态状态且进程不会消耗任何cpu和内存资源.鉴于会话现在没有请求锁定任何资源这就叫hang
在这种情况下可对实例转储系统状态来获得一些更详细更有用的信息.
ALTER SESSION SET EVENTS ‘IMMEDIATE TRACE NAME SYSTEMSTATE LEVEL XX’;
在oralce9.2.0.6或oracle10.1.0.4或在oracle10g中最高的版本的中这里的xx是266.执行上面的命令在你的user_dump_dest目录中会生成系统状态跟踪文件.
通过下面的查询可以得到问题进程的进程ID
SELECT pid FROM v$process
WHERE addr =
(SELECT paddr FROM v$session
WHERE sid = sid_of_problem_session);
系统状态转储文件包含了每一个进程的信息.可以通过搜索’PROCESS ‘来找到每一个进程的详细信息.通过搜索’waiting for’来找到当前正在等待的事件.

怎样收集errorstacks来论断性能问题

为了转储跟踪和errorstacks信息,可以使用操作系统进程ID或者oracle进程ID.比如可以通过oracle的sid来查询到操作系统进ID:

SELECT p.pid, p.SPID,s.SID
FROM v$process p, v$session s
WHERE s.paddr = p.addr
AND s.SID = &SID;

SPID是操作系统标识符
SID是oracle会话标识符
PID是oracle进程标识符

比如一个SPID是1254,pid是56如果使用SPID来生成转储和errorstacks信息可以执行下面的语句:

connect / as sysdba
ALTER SESSION SET tracefile_identifier = 'STACK_10046';
oradebug setospid 1254
oradebug unlimit
oradebug event 10046 trace name context forever,level 12
oradebug dump errorstack 3
oradebug dump errorstack 3
oradebug dump errorstack 3
oradebug tracefile_name
oradebug event 10046 trace name context off

如果使用PID来生成转储和errorstacks信息可以执行下面的语句:

connect / as sysdba
ALTER SESSION SET tracefile_identifier = 'STACK_10046';
oradebug setpid 56
oradebug unlimit
oradebug event 10046 trace name context forever,level 12
oradebug dump errorstack 3
oradebug dump errorstack 3
oradebug dump errorstack 3
oradebug tracefile_name
oradebug event 10046 trace name context off

其中oradebug tracefile_name命令会显示跟踪文件的名字和位置,在生成的跟踪文件名字会包含STACK_10046字符

如果要对当前会话收集errorstacks信息首先要找出当前会话的SPID或PID可以执行如下语句来获得:

SELECT p.pid, p.SPID,s.SID
FROM v$process p, v$session s
WHERE s.paddr = p.addr
AND s.audsid = userenv('SESSIONID') ;

或者

SELECT p.pid, p.SPID,s.SID
FROM v$process p,v$session s
WHERE s.paddr = p.addr
AND s.SID =
(SELECT DISTINCT SID
FROM V$MYSTAT);

对oracle中出现的坏块的处理方法

这篇文章介绍在oracle数据文件中出现一个或多个数据块坏块时的处理方法.当出现数据块坏块出误时对于每一个坏块都提供了以下信息:
1.包含这个坏块的数据文件的绝对文件号可以标示为”AFN”.
2.包含这个坏块的数据文件的文件名可以标示为”FILENAME”(如果知道文件号但不知道文件名那么可以执行select name from v$datafile where file#=&AFN来得到文件名,如果文件号在v$datafile中没有记录且AFN比参数db_files参数的值还大那么这个文件可能是临时文件.如果是这种情况可以执行select name from v$tempfile where file#=(&AFN-&DB_FILES_value)
3.数据文件中坏块的块号可以标示为”BL”
4.受坏块影响的表空间号和表空间名称可以标示为”TSN”和”TABLESPACE_NAME”.可以执行select ts# “TSN” from v$datafile where file#=&AFN;select tablespace_name from dba_data_files where file_id=&AFN来查询.
5.出现坏块的表空间的数据块大小可以标示为”TS_BLOCK_SIZE”.对于oracle9i来说可以执行select block_size from dba_tablespace where tablespace_name=(select tablespace_name from dba_data_files where file_id=&AFN);来查询数据块大小.对于oracle7.8.0和8.1在数据库中每一个表空间都有相同的数据块大小.对于这些版本可以使用show parameter db_block_size来显示数据块大小.

例如:ora-1578错识信息
ORA-01578: ORACLE data block corrupted (file # 7, block # 12698)
ORA-01110: data file 22: /oracle1/oradata/V816/oradata/V816/users01.dbf
从上面的错识信息可知:绝对文件号AFN是22,相对文件号RFN是7,数据块BL是12698,文件名FILENAME是/oracle1/oradata/V816/oradata/V816/users01.dbf,表空间号和表空间名可以用上面的查询得到.

处理坏块的步骤
导致坏块的原因有许多种例如:
坏的 IO 硬件/固件
OS 问题
Oracle 问题
对于执行过“UNRECOVERABLE”或“NOLOGGING”操作的数据库进行恢复在这种情况下可能产生 ORA-1578 错误

在遇到坏块时,我们通常无从了解根本原因,并且在大多数情况下,当前最迫切的是重新启动数据库并使其运行起来.
1. 确定坏块问题的范围,并确定这些问题是持久性问题还是暂时性问题
如果问题涉及范围很大,或错误不稳定,则关键在于先识别原因(检查硬件等).这点很重要.因为如果是底层硬件出现错误.恢复系统便毫无意义

2.更换或拆下任何有问题的或可疑的硬件

3.确定受到影响的数据库对象有哪些

4.选择最合适的数据库恢复/数据抢救选项

确定坏块问题的范围
每次发生坏块错误时,都应记下完整的错误消息,并查看该实例的告警日志和跟踪文件,以了解任何相关的错误.首先进行这些步骤非常重要,这可以评估该损坏是单个块,还是由于UNRECOVERABLE操作产生的错误,或是更严重的问题.

使用DBVERIFY扫描受影响的文件以及一切重要的文件也是不错的办法,这样可以检查是否有其他坏块,从而确定问题的范围.一旦确定了损坏的文件/块组合列表,即可使用以下步骤来帮助确定应采取何种措施:
1.完整记录初始错误,以及发生错误的应用程序的详细信息
2.及时地保存从告警日志中首次 (FIRST) 记录到问题前数小时到当前时间点所提取的内容
3.保存告警日志中提到的任何跟踪文件
4.记录最近遇到的任何 OS 问题
5.记录是否正在使用任何特殊功能,例如:ASYNC IO、快速写入磁盘选项等
6.记录当前的备份位置(日期、类型等)
7.记录数据库是否处于ARCHIVELOG 模式,例如:在SQL*Plus(或 Server Manager)中运行“ARCHIVE LOG LIST”

更换或拆下可疑硬件
大多数坏块问题是由故障硬件导致的.如果出现硬件错误或可疑组件,最好进行修复,或者在执行恢复操作之前,确保在单独的磁盘子系统上有足够的可用空间用于恢复,您可以使用以下步骤移动数据文件:
1.确保要迁移的文件已离线或数据库实例处于 MOUNT 状态(未打开)
2. 将该数据文件物理还原(或复制)到新位置 例如:/newlocation/myfile.dbf
3.将该文件的新位置告知 Oracle.
例如:ALTER DATABASE RENAME FILE ‘/oldlocation/myfile.dbf’ TO ‘/newlocation/myfile.dbf’;
(请注意,您不能对临时文件进行重命名,而应删除临时文件并在新位置重新创建)
4.使相关数据文件/表空间上线(如果数据库已打开)

注意:
如果存在多个错误(不是由于 NOLOGGING操作导致的)或受影响文件所在的OS层面出现错误或错误是暂时性的且游离不定,那么,如果不解决底层问题或准备另外的磁盘空间,那么进行任何操作都是毫无意义的.

如果使用了任何特殊IO选项,例如direct IO,async IO或类似的选项,最好将其禁用,以消除这些选项成为潜在问题原因的可能性

确定受影响的对象有哪些
在决定如何恢复之前,最好先确定哪些对象受到了影响,因为坏块可能发生在那些容易被重新创建的对象中.例如,对于只有5行数据的表中发生的坏块,删除并重新创建表可能要比执行恢复快得多.

对于每个坏块,请收集下表中的信息.进行此操作的步骤如下所述。
1.初始错误
2.绝对文件号AFN
3.相关文件号RFN
4.块编号BL
5.表空间
6.段类型
7.段所有者.名称
8.相关对象
9.恢复选项

在Oracle8/8i/9i/10g中;绝对文件号和相关文件号通常是一样的,但也可能不同(尤其是在数据库是由Oracle7迁移而来的情况下).要获得正确的AFN和RFN编号,否则您可能最终抢救的是错误的对象.

下列查询将显示数据库中数据文件的绝对和相关文件号:
SELECT tablespace_name, file_id “AFN”, relative_fno “RFN” FROM dba_data_files;

在Oracle8i/9i/10g中:除了上述关于Oracle8 的说明外,从 Oracle8i开始将拥有临时文件.下列查询将显示数据库中临时文件的绝对和相关文件号:
SELECT tablespace_name, file_id+value “AFN”, relative_fno “RFN”
FROM dba_temp_files, v$parameter WHERE name=’db_files’;

在Oracle7中:“绝对文件号”和“相关文件号”使用相同的文件号

“段类型”,“所有者”,“名称”和“表空间”
在给定坏块的绝对文件号“&AFN”和块编号“&BL”的情况下,下列查询将显示对象的段类型,所有者和名称,数据库必须打开才能使用此查询:
SELECT tablespace_name, segment_type, owner, segment_name
FROM dba_extents
WHERE file_id = &AFN
and &BL between block_id AND block_id + blocks – 1;

如果坏块位于临时文件中,则上述查询将不会返回任何数据,对于临时文件,“段类型”应为“TEMPORARY”

如果上述查询未返回行,也可能是因为坏块是本地管理表空间 (Locally Managed Tablespace, LMT)中的段头.当坏块为LMT中的段头块时,上述查询将在alert.log 中生成一个坏块消息,但查询不会失败.在这种情况下,请使用以下查询:
SELECT owner, segment_name, segment_type, partition_name
FROM dba_segments
WHERE header_file = &AFN and header_block = &BL;

按段类型分类的“相关对象”和可能的“恢复选项”:
相关对象和能够使用的恢复选项取决于SEGMENT_TYPE.对于各种最常见的段类型,其他查询和可能的恢复选项如下所示:
CACHE
如果段类型为 CACHE,请再次检查您是否输入了正确的 SQL语句 和参数。
恢复选项:可能需要恢复数据库。

CLUSTER
如果段类型为 CLUSTER,则应确定它包含哪些表。
例如:
SELECT owner, table_name
FROM dba_tables
WHERE owner=’&OWNER’
AND cluster_name=’&SEGMENT_NAME’;

恢复选项:
如果所有者为“SYS”可能需要恢复数据库。

对于非数据字典cluster,可能的选项包括:
恢复或 抢救cluster中所有表的数据,然后重新创建cluster及其所有表,
cluster可能包含多个表,因此在做出决策之前,最好先收集cluster中每个表的信息。

INDEX PARTITION
如果段类型为INDEX PARTITION,请记录名称和所有者,然后确定哪些分区受到影响:
SELECT partition_name
FROM dba_extents
WHERE file_id = &AFN
AND &BL BETWEEN block_id AND block_id + blocks – 1;
然后按照处理INDEX段的步骤继续下面的操作.
恢复选项:
使用下列语句可以重建索引分区:
ALTER INDEX xxx REBUILD PARTITION ppp;

INDEX
如果段类型为INDEX,对于非字典INDEX或INDEX PARTITION,确定索引位于哪个表中:
例如:
SELECT table_owner, table_name
FROM dba_indexes
WHERE owner=’&OWNER’
AND index_name=’&SEGMENT_NAME’;
并确定索引是否支持约束:
例如:
SELECT owner, constraint_name, constraint_type, table_name
FROM dba_constraints
WHERE owner=’&TABLE_OWNER’
AND constraint_name=’&INDEX_NAME’;

CONSTRAINT_TYPE 的可能值包括:
P 索引支持主键约束。
U 索引支持唯一约束。
如果索引支持主键约束(类型“P”),则确认主键是否被任何外键约束引用:
例如:
SELECT owner, constraint_name, constraint_type, table_name
FROM dba_constraints
WHERE r_owner=’&TABLE_OWNER’
AND r_constraint_name=’&INDEX_NAME’
选项:
如果所有者为“SYS”,可能需要恢复数据库。
对于非字典索引,可能的选项包括:
恢复或 重建索引(任何相关联的约束会随之禁用/启用)

ROLLBACK
如果段类型为ROLLBACK,因为 ROLLBACK 段坏块需要特殊处理。
选项可能需要恢复数据库。

TYPE2 UNDO
TYPE2 UNDO 是系统管理的undo段,它是 rollback段的一种特殊形式.这些段的坏块需要特殊处理.
选项可能需要恢复数据库。

TABLE PARTITION
如果段类型为TABLE PARTITION,请记录名称和所有者,然后确定哪些分区受到影响:
SELECT partition_name
FROM dba_extents
WHERE file_id = &AFN
AND &BL BETWEEN block_id AND block_id + blocks – 1;
然后按照处理TABLE段的步骤继续下面的操作.
选项:
如果所有坏块均位于同一个分区,则此时可以采取的一个做法是用一个空表EXCHANGE坏块所在的分区,这可以让应用程序继续运行(无法访问坏块所在的分区中的数据),然后可以从之前的空表中提取任何未损坏的数据

TABLE
如果所有者为“SYS”,可能需要恢复数据库。
对于非字典 TABLE 或 TABLE PARTITION,确定表中存在哪些索引:
例如:
SELECT owner, index_name, index_type
FROM dba_indexes
WHERE table_owner=’&OWNER’ AND table_name=’&SEGMENT_NAME’;
并确定表中是否存在任何主键:
例如:SELECT owner, constraint_name, constraint_type, table_name
FROM dba_constraints
WHERE owner=’&OWNER’
AND table_name=’&SEGMENT_NAME’ AND constraint_type=’P’;
如果存在主键,则确认它是否被任何外键约束引用:
例如:
SELECT owner, constraint_name, constraint_type, table_name
FROM dba_constraints
WHERE r_owner=’&OWNER’
AND r_constraint_name=’&CONSTRAINT_NAME’;
选项:
如果所有者为“SYS”,可能需要恢复数据库。
对于非字典表,可能的选项包括:
恢复或 抢救表(或分区)中的数据,然后重新创建表(或分区)或忽略坏块(例如:使用DBMS_REPAIR标记需要跳过的问题块)

IOT(索引组织表)
IOT 表中的坏块应按照表或分区表中的处理方式来处理。
唯一的例外是如果 PK 损坏。
IOT表的PK就是表本身它不能被删除和重新创建
选项:
如果所有者为“SYS”,可能需要恢复数据库。
对于非字典表,可能的选项包括:
恢复或 抢救表(或分区)中的数据,然后重新创建表(或分区)或忽略坏块(DBMS_REPAIR不适用于IOT)

LOBINDEX
确定LOB属于哪个表:
SELECT table_name, column_name
FROM dba_lobs
WHERE owner=’&OWNER’ AND index_name=’&SEGMENT_NAME’;
如果表的所有者为“SYS”可能需要恢复数据库。
不可以重建 LOB 索引,因此您必须将该问题作为受影响的表中LOB列上的坏块来处理。
选项:
如果所有者为“SYS”可能需要恢复数据库。
对于非字典表,可能的选项包括:
恢复或 抢救表(及其 LOB 列)中的数据,然后重新创建表,忽略坏块的做法通常不可取,除非不大可能对表中的问题列执行任何进一步的 DML 操作。

LOBSEGMENT
确定 LOB 属于哪个表:
例如:
SELECT table_name, column_name
FROM dba_lobs
WHERE owner=’&OWNER’
AND segment_name=’&SEGMENT_NAME’;
如果表的所有者为“SYS”,可能需要恢复数据库。
对于非字典表,要查找引用损坏的 LOB 块的具体行可能比较困难,因为报告的错误中不会显示表中的哪一行数据包含损坏的 LOB 数据。
通常可以参考发生该错误的应用程序日志、任何SQL_TRACE、会话的10046 跟踪文件(如果有),或通过在会话中设置事件“1578 trace name errorstack level 3”,查看是否有助于标识当前的 SQL/绑定/行。
例如:
ALTER SYSTEM SET EVENTS ‘1578 trace name errorstack level 3’;
然后等待应用程序触发该错误,并查找跟踪文件。
如果没有任何线索,您可以构建 PLSQL 块,逐行扫描问题表以提取 LOB 列数据,扫描将一直循环进行,直至发生错误。此方法可能需要一段时间,但它应该可以找到引用了损坏的 LOB 块的数据行的主键或 ROWID。
例如:
set serverout on
exec dbms_output.enable(100000);
declare
error_1578 exception;
pragma exception_init(error_1578,-1578);
n number;
cnt number:=0;
badcnt number:=0;
begin
for cursor_lob in (select rowid r, &LOB_COLUMN_NAME L
from &OWNER .. &TABLE_NAME) loop
begin
n := dbms_lob.instr(cursor_lob.L, hextoraw(‘AA25889911’), 1, 999999);
exception
when error_1578 then
dbms_output.put_line(‘Got ORA-1578 reading LOB at ‘ ||
cursor_lob.R);
badcnt := badcnt + 1;
end;
cnt := cnt + 1;
end loop;
dbms_output.put_line(‘Scanned ‘ || cnt || ‘ rows – saw ‘ || badcnt ||
‘ errors’);
end;
/
损坏的 LOB 块可能仅显示为旧版本(为保证一致性读取),且该块未被重新使用,在这种情况下,所有表中所有行都可以访问,但一旦该块被回收重新使用,就不可以插入/更新 LOB 列了。
选项:
如果所有者为“SYS”,可能需要恢复数据库。
对于非字典表,可能的选项包括:
恢复或抢救表(及其 LOB 列)中的数据,然后重新创建表或忽略坏块(不可以在 LOB 段上使用 DBMS_REPAIR)

TEMPORARY
如果段类型为TEMPORARY,则坏块不会影响永久对象.检查发生问题的表空间是否正在被用作TEMPORARY表空间:
SELECT count(*) FROM dba_users
WHERE temporary_tablespace=’&TABLESPACE_NAME’;
选项:
如果是 TEMPORARY_TABLESPACE,则可能可以创建新的临时表空间,并将所有用户切换到该表空间,然后删除有问题的表空间。
如果不是临时表空间,则该块不会再被读取,而且会在下次使用时被重新格式化 — 如果问题的根本原因已经得到解决,则不应再发生该错误。
通常情况下,不需要进行任何还原,但如果磁盘可能有问题,且表空间包含有用数据,则最好对数据库中受影响的文件进行恢复

“无返回行”
如果没有包含坏块的extent,则首先再次检查查询中使用的参数.如果您确定文件号和块编号是正确的,且不属于 DBA_EXTENTS 中的某个对象,则执行以下操作:
再次检查相关文件是否为临时文件。请注意,临时文件的文件号取决于数据库初始化参数 DB_FILES,因此对该参数的任何更改都会改变错误中报告的绝对文件号。

DBA_EXTENTS 不包含本地管理表空间中用于本地空间管理的块

如果您在数据库运行查询语句的时间点与出错的时间点不相同,那么问题对象可能已经被删除,因此针对 DBA_EXTENTS 的查询可能不会显示任何行。

如果您正在调查的错误由 DBVERIFY 报告,则 DBV 将检查所有块,而不管它们是否属于某个对象。因此,坏块可能存在于数据文件中,但却未被任何对象使用。
选项:
未使用的 Oracle 块上的错误可以忽略,因为如果需要使用该块,Oracle 会创建新的块映像(格式化),因此,该块上的任何问题将永不会被读取。
如果您怀疑该块可能是空间管理块,则可以使用 DBMS_SPACE_ADMIN 包来帮助您进行检查:
exec DBMS_SPACE_ADMIN.TABLESPACE_VERIFY(‘&TABLESPACE_NAME’);
以上命令会将不一致写入跟踪文件,但如果遇到致命的坏块,它将报告如下错误:
ORA-03216: Tablespace/Segment Verification cannot proceed
位图空间管理块上发生的错误通常可以通过运行以下命令来修正:
exec DBMS_SPACE_ADMIN.TABLESPACE_REBUILD_BITMAPS(‘&TABLESPACE_NAME’);
对于每个坏块,如果需要尝试并确定实际坏块原因,则收集如下物理证据也是一个比较好的方法:
i) 坏块及位于其任意一侧的块的操作系统 HEX 转储。
在 UNIX 上:
dd if=&FILENAME bs=&TS_BLOCK_SIZE skip=&BL-1 count=3 of=BL.dd
例如:对于BL=1224:
dd if=ts11.dbf bs=4k skip=1223 count=3 of=1223_1225.dd
在 VMS 上:
其中 XXXX=操作系统块编号(512 字节块中)
要计算此值,用报告的块编号乘以“&TS_BLOCK_SIZE/512”。

ii) 处于 ARCHIVELOG 模式时,复制出错时间前后的归档日志文件的安全副本,最好包括报告错误前数小时的日志文件。并且,保存问题数据文件在出错前的所有副本,因为之前的数据文件映像以及 redo 记录有助于找出错误原因DBV 通常可用于检查问题是否存在于文件的备份副本中).理想的情况是获得没有报告坏块的数据文件备份映像,以及从该时间点开始到首次报告坏块时间之后不久的时段内的所有 redo 记录。

iii) 获得问题块的 Oracle 转储:
ALTER SYSTEM DUMP DATAFILE ‘&FILENAME’ BLOCK &BL;

(4) 选择恢复选项

现在,最佳的恢复选项取决于受影响的对象。前面第 (3) 部分中的说明应该已经重点介绍了针对每个受影响对象的主要可用选项。选择的实际恢复方法可能包含以下一种或多种混合方法:
是否需要进行任何恢复操作?
如果错误发生在TEMPORARY 表空间中,或位于不再属于任何数据库对象的块中,则无需进行任何操作.

可以使用完全恢复吗?
要选用完全恢复,必须满足如下条件:
数据库处于 ARCHIVELOG 模式(“ARCHIVE LOG LIST”命令显示 Archivelog模式)

拥有受影响文件的完好备份。请注意,在某些情况下,坏块可能已经存在,但在很长一段时间内未被发现。如果最近的数据文件备份仍包含坏块,那么只要您拥有所有必需的归档日志,就可以尝试使用更早的备份。
(通常可以使用 DBV START= / END= 选项来检查位于某个备份文件的恢复副本中的特定块是否损坏)

从备份时间开始到当前时间点的所有归档日志均可用

当前的在线日志均可用且完好无缺

错误不是由运行NOLOGGING 操作之后执行的恢复所导致的
如果满足上述条件,完全恢复通常是首选方法
但请注意:
(a) 如果事务回滚已发现坏块位于对象上,而非 rollback 段本身,则 undo 操作可能已被放弃。在这种情况下,可能需要在恢复完成后重建索引/检查数据完整性。

(b) 如果要恢复的文件包含自上次备份以来执行的 NOLOGGING 操作的数据,在使用了数据文件或数据库恢复的情况下,这些块将被标记为“坏块”。在某些情况下,这会使情况更加糟糕。

如果执行数据库恢复后坏块仍然存在,则表示所有备份都包含坏块,底层错误仍存在,或问题通过redo 重现。在这些情况下,需要选择其他一些恢复选项。

如果不需要从对象本身提取任何数据,能否删除或重新创建该对象?
您可以删除对象或从脚本/最近导出的副本重新创建对象。一旦删除一个对象后,该对象中的块将被标记为“空闲”,并且该块在被分配到新对象时将被重新格式化.明智的做法是,对表进行重命名,而不是删除,除非您完全确定不再需要其中的数据。

对于表分区,只需要删除受影响的分区。例如:ALTER TABLE …DROP PARTITION …

如果坏块影响到分区段头,或者包含分区头的文件处于离线状态,则 DROP PARTITION 可能会失败。在这种情况下,首先将其更换为具有相同定义的表,之后仍然可以删除该分区。
例如:ALTER TABLE ..EXCHANGE PARTITION ..WITH TABLE ..;
最常见的可重建对象为索引。始终在处理表中的索引问题之前处理表坏块

对于任何段,如果您拥有坏块的绝对文件号和块号,则可使用以下快速提取对象 DDL 的方法:
set long 64000
select dbms_metadata.get_ddl(segment_type, segment_name, owner)
FROM dba_extents
WHERE file_id=&AFN AND &BL BETWEEN block_id AND block_id + blocks -1;

是否需要在重新创建对象之前抢救数据?
如果问题位于定期更新的关键应用表上,则可能需要尽可能多地抢救表中数据,然后重新创建该表。

当前忽略坏块是否可取?
在某些情况下,最直接的选项可能就是忽略坏块,并阻止应用程序对它进行访问。

最后的选项
将数据库或表空间恢复到较早的时间点(通过时间点恢复)或还原出现坏块前的冷备份或使用现有导出文件

完全恢复
如果数据库处于ARCHIVELOG 模式下,且拥有受影响文件的完好备份,则恢复通常为首选方法.这不保证可以解决问题,但的确可以有效的解决大部分坏块问题.如果恢复再次引发问题,则返回到以上选项列表并选择其他方法.

如果使用的是Oracle9i(或更高版本),则可以使用RMAN BLOCKRECOVER命令执行块级恢复。

如果使用的是较早版本的Oracle,则可以执行数据文件恢复(数据库其他部分可以继续运行),或数据库恢复(需要关闭数据库)

如果使用的是Oracle 11g(或更高版本,则可以使用“Data Recovery Advisor(数据恢复指导)”.

块级恢复
自Oracle9i版本起,RMAN允许恢复单个块,同时数据库的其他部分(包括数据文件中的其他块)仍可以进行正常访问.请注意,块级恢复只能将块完全恢复到当前时间点.要使用此选项恢复单个块,不一定要使用 RMAN 进行备份.
例如:
实际情况是,文件6的块30上发生ORA-1578错误,可能是由于介质问题导致的坏块,且您拥有该文件的完好冷备份映像,并已还原到“…/RESTORE/filename.dbf”.假设所有归档日志均存在(位于默认位置),则可以通过RMAN使用以下命令序列执行块级恢复:
rman nocatalog
connect target
catalog datafilecopy ‘…/RESTORE/filename.dbf’;
run {blockrecover datafile 6 block 30;};
此操作将使用注册的数据文件备份映像和任何需要的归档日志来执行块恢复,仅将有问题的块恢复到当前时间点.

数据文件恢复
数据文件恢复包括下列步骤.如果有多个文件,则针对每个文件重复执行这些步骤,或参阅下面的“数据库恢复”.当数据库处于 OPEN 或 MOUNTED 状态时,均可使用这些步骤.
使受影响的数据文件离线
例如:ALTER DATABASE DATAFILE ‘name_of_file’ OFFLINE;

将文件复制到安全位置(以防备份损坏)

将文件的最新备份还原到完好的磁盘上

使用DBVERIFY检查还原的文件是否有坏块

假设还原的文件完好,则将数据文件重命名并保存到新位置(如果不是原来的位置)
例如:ALTER DATABASE RENAME FILE ‘old_name’ TO ‘new_name’;

恢复数据文件
例如:RECOVER DATAFILE ‘name_of_file’;

使数据文件上线
例如:ALTER DATABASE DATAFILE ‘name_of_file’ ONLINE;

数据库恢复
数据库恢复通常包含以下步骤:
关闭数据库(使用选项 immediate 或 abort)

将待恢复的所有文件的当前副本复制到安全位置

将备份文件还原到完好的磁盘上

请勿还原控制文件或在线REDO 日志文件

使用DBVERIFY检查还原的文件

启动数据库到MOUNT状态(startup mount)

对任何需要重新定位的数据文件进行重命名
例如:ALTER DATABASE RENAME FILE ‘old_name’ TO ‘new_name’;

确保所有必需的文件在线
例如:ALTER DATABASE DATAFILE ‘name_of_file’ ONLINE;

恢复数据库
例如:RECOVER DATABASE

打开数据库
例如:ALTER DATABASE OPEN;

一旦执行了完全恢复,最好在允许使用之前先检查数据库:
针对每个问题对象运行“ANALYZE VALIDATE STRUCTURE CASCADE”,检查表/索引是否存在不匹配。如果有任何 und 操作曾被放弃,此命令可能会显示不匹配,此时需要重建索引。

在应用程序级别检查表中数据的逻辑完整性。

重建索引
损坏对象为用户索引时,如果底层表没有损坏,则可以删除并重建该索引。

如果底层表也已经损坏,则应在重建任何索引之前先解决该表的坏块。

如果收集的信息表示索引有从属外键约束,则需要执行以下操作:
ALTER TABLE DISABLE CONSTRAINT ;

使用以下命令重建主键
ALTER TABLE

DISABLE CONSTRAINT ;
DROP INDEX ;
CREATE INDEX
.. with appropriate storage clause
ALTER TABLE

ENABLE CONSTRAINT ;

启用外键约束
ALTER TABLE ENABLE CONSTRAINT ;

对于索引分区,以执行以下命令:
ALTER INDEX …REBUILD PARTITION …;

注意:
(1) 不要使用“ALTER INDEX .. REBUILD”命令重建损坏的非分区索引,这一点非常重要,因为此操作通常会尝试从包含坏块的现有索引段中构建新索引..“ALTER TABLE … REBUILD ONLINE”和“ALTER INDEX … REBUILD PARTITION …”不会从旧索引段中构建新索引,因此可以使用。

(2) 如果新索引包含的列为现有索引的子集,则 Create INDEX 可以使用现有索引中的数据,因此,如果您有两个损坏的索引,应在重建之前将两个都删除。

(3) 重建索引时,请确保使用正确的存储选项。

抢救表中数据
如果损坏的对象为TABLE 或 CLUSTER 或 LOBSEGMENT,则必须明白,坏块内的数据已经丢失.部分数据可能可以从块的HEX转储中,或从索引涵盖的列中抢救回来.

由于可能需要从索引中抢救坏块中的数据,因此最好不要删除任何现有索引,直至所有需要的数据提取完成。

从包含坏块的表中提取数据有多种方法。选择最恰当的方法,详细信息如下所述.这些方法的目的是从可访问的表块中提取尽可能多的数据.通常,将损坏的表重命名是一个比较好的方法,这样就可以使用正确的名称创建新对象.
例如:RENAME TO ;

从坏块表中提取坏块周围数据的方法
(1) 从Oracle 7.2开始(包括 Oracle 8.0、8.1 和 9i)可以跳过表中的坏块。
这是到目前为止最简单的提取表数据的方法,用DBMS_REPAIR.SKIP_CORRUPT_BLOCKS or Event 10231

如果坏块位于IOT overflow 段,则应使用相同的方法,不同的是使用Event 10233和全索引扫描

请注意,此方法只适用于块的“包装”已被标记为“坏块”的情况。例如:如果块报告 ORA-1578 错误。如果问题为 ORA-600 或其他非ORA-1578 错误,则通常可以使用 DBMS_REPAIR 将表中坏块标记为“软坏块”。这样在您访问该数据块时,系统将显示 ORA-1578错误,从而可以使用 DBMS_REPAIR.SKIP_CORRUPT_BLOCKS。

注意:被“FIX_CORRUPT_BLOCKS”程序标记为“坏块”的块在任何还原/恢复操作之后还将被标记为“坏块”.

使用DBMS_REPAIR进行此操作概括起来步骤如下:
使用DBMS_REPAIR.ADMIN_TABLES 创建管理表

使用DBMS_REPAIR.CHECK_OBJECT 找到问题块

在损坏问题块之前将其中所有完好的数据导出。

使用DBMS_REPAIR.FIX_CORRUPT_BLOCKS将找到的问题块标记为“坏块”,然后它们就会显示 ORA-1578

如果需要,使用 DBMS_REPAIR.SKIP_CORRUPT_BLOCKS跳过表中的坏块。

(2) 从 Oracle 7.1 开始,可以使用 ROWID 范围扫描.此功能的语法较为复杂,但可以使用 ROWID提示选择坏块周围的数据.

(3) 如果存在主键,则可以通过此索引选择表数据。也可以通过任何其他索引选择一些数据。此方法较慢,花费时间较长,通常只有 Oracle 7.0 版本才使用

(4) 有多种抢救程序/PLSQL 脚本可用于抢救表中的数据。与上述方法相比,这些方法在设置和使用方面需要花费更长的时间,但常常能够处理除 ORA-1578 之外的各类坏块

从包含损坏的LOBSEGMENT 块的表中提取数据的方法:
在 LOB 段上不可以使用 DBMS_REPAIR,如果坏块 LOB 块未被表中的任何行引用,则应该可以使用 CREATE TABLE as SELECT (CTAS)来按选择创建表,或按原样导出/删除/导入该表。

如果坏块LOB 块被某个行引用,则应该可以使用不包括问题行的WHERE谓词进行选择或导出

注意:可以将问题行的LOB列值更新为NULL,从而使SELECT操作不再返回ORA-1578错误,但是坏块将等待被重新使用,随着对行中的 LOB列进行INSERT或UPDATE操作,当有问题的块被重新使用时,最后还是会报ORA-1578错误,那时的情况比已知行出现坏块更糟糕.因此,只有您打算立刻重新创建表,才应该将LOB列设为NULL.

从坏块本身提取数据
由于坏块本身已经“损坏”,则从该块中提取的任何数据都应被视为可疑数据,从坏块本身获取数据行的主要方法包括:
对于 TABLE 的块,Oracle Support 可以使用一款尝试解释块内容的工具。

使用表中现有索引,利用落在坏块内的ROWID 来提取索引所涵盖的列数据,上文提到的 ROWID 范围扫描文章在接近结束时对此内容有所介绍:
对于 Oracle8/8i,请参阅 Document 61685.1
对于 Oracle7,请参阅 Document 34371.1

在 redo 流上可以使用 LogMiner 来查找向问题块加载数据的初始插入/更新操作。此处的主要因素是数据实际被放入问题块的时间.例如,行2可能在昨天已插入,而行1可能在1年前已插入.

忽略坏块
出错时可以忽略坏块并接受报告的错误,或在应用程序级别阻止对出问题的块行进行访问。
例如:如果问题块/行位于子表中,则可以在应用程序级别阻止对父表中对应行的访问,从而子行就永不会被访问(但要注意级联类约束)

这样做可能不利于批量访问数据的报告和其他任务,因此,为了阻止块在被访问时报错,前面所述的DBMS_REPAIR选项也不失为一个可取的方法.使用这种方法标记并跳过坏块提供了一种短期的解决方案.从而在计划停机时可以尝试进行完全数据抢救和/或恢复,或留出更多时间在第二个(克隆)数据库上尝试其他恢复选项.但请注意,使用DBMS_REPAIR.FIX_CORRUPT_BLOCKS标记块坏块将导致标记的块在恢复后还是“坏块”。

忽略坏块对于快速老化且即将被清除的数据而言是比较好的选择(例如,在按日期分区的表中,较老的分区将在某时间点被删除).

忽略LOB段上的坏块
在应用程序级别,可以忽略损坏的LOB列,直到可以重新构建该表.确保不出现上述“警告”中的情形的一种方法是确保应用程序只能通过表上的包含WHERE 谓词的视图来访分表中的数据.
例如:假设表 MYTAB(a number primary key,b clob)有一行或多行指向损坏的 LOB 数据。
ALTER TABLE MYTAB ADD ( BAD VARCHAR2(1) );

CREATE VIEW MYVIEW AS SELECT a,b FROM MYTAB WHERE BAD is null;

对任何问题行设置 BAD=’Y’

如果只通 MYVIEW 访问 MYTAB,该行将永不可见,因此也无法更新,从而实现了坏块条目隔离,直到问题解决.

很明显,此示例更多的是一个设计时解决方案,但某些应用程序可能已有类似机制,且可能只通过某个视图(或通过 RLS 策略)访问数据,从而提供某些选项来隐藏问题行。

针对忽略坏块的警告
虽然可以忽略坏块,但需要注意的是,坏块在运行DBVERIFY,RMAN 备份时仍然会以警告/错误等形式出现。请务必仔细记录您将在这些工具中看到的任何坏块,尤其是您期望在使用RMAN时跳过的任何块(例如,设置了 MAX_CORRUPT),并确保在清除坏块后移除任何对错误的“接受”选项.
例如:假设坏块已处理为忽略坏块,并在应用程序级别跳过问题行。RMAN可能被配置为在备份时接受坏块。然后在稍后的表重组期间重新创建表。如果 RMAN 配置未及时更新以反映目前已无任何错误,则 RMAN 可能会忽略稍后出现的某些其他坏块。

此外,还有重要的一点需要注意,忽略table段中的坏块可能导致查询返回不一致的结果。
例如:设置了 SKIP_CORRUPT 的表可能出现不同的结果,具体取决于是使用了了索引扫描还是表访问,其他报告可能只是报错.。

请注意,如果忽略坏块但使用DBMS_REPAIR.FIX_CORRUPT_BLOCKS标记,系统会向坏块中写入redo信息,这可能会限制后续的恢复选项.

最后的选项
如果你有standby环境(物理或逻辑),请首先对其进行检查。

无论问题发生在何种类型的块上,均可使用一种可能的选项,即将数据库或问题表空间恢复到出现坏块之前的某个时间点.此选项的困难之处在于,并不总能知道问题首次出现的时间.

DBVERIFY通常可用于检查还原的文件是否存在坏块.尤其是,START= / END= DBV选项可用于在还原的备份映像上快速进行首次测试,以检查问题块本身是否出错。

下面列出了一些可用于进行恢复操作的最终选项,当出现其中一种或多种情况:
您丢失了非常重要的数据文件(或数据文件出现坏块),而没有问题文件的正常备份(无坏块)
既不处于ARCHIVELOG 模式,也没有自文件创建以来的全部归档日志
完全恢复后仍重复出现问题

最后的机会:
请注意,如果丢失了数据文件的所有副本,但仍具有自文件创建以来的全部归档日志,则仍有可能恢复该文件。
例如:
ALTER DATABASE CREATE DATAFILE ‘….'[as ‘…’] ;

RECOVER DATAFILE ‘….’

ALTER DATABASE DATAFILE ‘….’ONLINE;
如果您遇到这种情况,请在继续下面的操作之前先尝试使用这些步骤来恢复数据文件。

如果您到达这一步,就说明没有其他办法可以将文件恢复到当前时间点.此时最好关闭实例,并对当前数据库进行备份,以便在选用的措施失败后仍然能够回退到当前时间点.(例如:如果发现备份坏块).

可用的一些选项概述如下:
恢复到早期的冷备份
例如:如果处于 NOARCHIVELOG 模式
从冷备份建立克隆数据库
并提取(导出)问题表
或传输问题表空间

使用基于时间点的恢复将数据库恢复到一致的时间点
需要完好备份和任何所需的归档日志
必须还原所有文件且将整个数据库前滚到恰当的时间点。
可以在克隆数据库中执行基于时间点的恢复,然后将问题表空间传输到问题数据库,或将问题表利用导出/导入工具从克隆数据库导入到问题数据库.

表空间基于时间点的恢复
可以仅对受影响的表空间执行基于时间点的恢复.

从逻辑导出/副本重新创建数据库
需要具有完好的数据库逻辑备份
注意:要使用此选项,必须重新创建数据库。
与其他选项一样,可以在克隆数据库中进行重新创建,只为获得问题表的完好映像.

总之做好备份是DBA最重要的工作.

oracle 10g expdp导出报错ora-4031的解决方法

数据库是10.2.0.4 操作系统是aix,在执行expdp导出多个方案对象时报ORA-39014,ORA-39029,ORA-31671,ORA-39079,ORA-06512,ORA-04031:错误信息如下:
Export: Release 10.2.0.4.0 – 64bit Production on Monday, 17 February, 2014 9:46:52

Copyright (c) 2003, 2007, Oracle. All rights reserved.
;;;
Connected to: Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 – 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
Starting “SYSTEM”.”SYS_EXPORT_SCHEMA_01″: system/******** directory=dump_RLZY dumpfile=ybcwfull_140217_0946.dmp logfile=ybcwfull_140217_0946.log schemas=ZW
1001,ZW1002,ZW1003,ZW1004,ZW1005,ZW1006,ZW1101,ZW1102,ZW1103,ZW1104,ZW1105,ZW1106,ZW1201,ZW1202,ZW1203,ZW1204,ZW1205,ZW1206,ZW1301,ZW1302,ZW1303,ZW1304,ZW13
05,ZW1306,ZW2001,ZW2002,ZW2003,ZW2004,ZW2005,ZW2006,ZW3001,ZW3002,ZW3003,ZW3004,ZW3005,ZW3006,ZW4001,ZW4002,ZW4003,ZW4004,ZW4005,ZW4006,ZW5001,ZW5002,ZW5003
,ZW5004,ZW5005,ZW5006,ZW6001,ZW6002,ZW6003,ZW6004,ZW6005,ZW6006,ZW7001,ZW7002,ZW7003,ZW7004,ZW7005,ZW7006,ZW8001,ZW8002,ZW8003,ZW8004,ZW8005,ZW8006,ZW9001,Z
W9002,ZW9003,ZW9004,ZW9005,ZW9006,ZW9999
Estimate in progress using BLOCKS method…
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 997.3 MB
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/ROLE_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/SEQUENCE/SEQUENCE
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/INDEX/INDEX
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/PACKAGE/PACKAGE_SPEC
Processing object type SCHEMA_EXPORT/FUNCTION/FUNCTION
Processing object type SCHEMA_EXPORT/PROCEDURE/PROCEDURE
Processing object type SCHEMA_EXPORT/PACKAGE/COMPILE_PACKAGE/PACKAGE_SPEC/ALTER_PACKAGE_SPEC
Processing object type SCHEMA_EXPORT/FUNCTION/ALTER_FUNCTION
Processing object type SCHEMA_EXPORT/PROCEDURE/ALTER_PROCEDURE
Processing object type SCHEMA_EXPORT/VIEW/VIEW
Processing object type SCHEMA_EXPORT/PACKAGE/PACKAGE_BODY
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/REF_CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/TRIGGER
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
………………
. . exported “ZW9005″.”GLSP_KMYS_TEMP” 0 KB 0 rows
. . exported “ZW9005″.”GLSP_KMZJS_TEMP” 0 KB 0 rows
. . exported “ZW9005″.”GLSP_KMZXFX_TEMP” 0 KB 0 rows
. . exported “ZW9005”.”GLSP_PZYJZ_TEMP1″ 0 KB 0 rows
. . exported “ZW9005”.”GLSP_PZYJZ_TEMP2″ 0 KB 0 rows
. . exported “ZW9005”.”GLSP_PZYJZ_TEMP3″ 0 KB 0 rows
. . exported “ZW9005”.”GLSP_PZYJZ_TEMP4″ 0 KB 0 rows
. . exported “ZW9005”.”GLSP_PZYJZ_TEMP5″ 0 KB 0 rows
. . exported “ZW9005”.”GLSP_PZYJZ_TEMP6″ 0 KB 0 rows
ORA-39014: One or more workers have prematurely exited.
ORA-39029: worker 1 with process name “DW01” prematurely terminated
ORA-31671: Worker process DW01 had an unhandled exception.
ORA-39079: unable to enqueue message DG,KUPC$C_1_20140217094653,KUPC$A_1_20140217094654,MCP,50443,Y
ORA-06512: at “SYS.DBMS_SYS_ERROR”, line 86
ORA-06512: at “SYS.KUPC$QUE_INT”, line 924
ORA-04031: unable to allocate 2072 bytes of shared memory (“streams pool”,”unknown object”,”streams pool”,”kodpaih3 image”)
ORA-06512: at “SYS.KUPW$WORKER”, line 1397
ORA-06512: at line 2

Job “SYSTEM”.”SYS_EXPORT_SCHEMA_01″ stopped due to fatal error at 10:28:18

从错误信息中可以看到ORA-04031: unable to allocate 2072 bytes of shared memory (“streams pool”,”unknown object”,”streams pool”,”kodpaih3 image”)
从字面上理解是在给streams pool分配内存时出错造成的,MOS上有一篇文件档
DataPump Export (EXPDP) Fails With Error ORA-4031 (“streams pool”, …) (文档 ID 457724.1)
In this Document

Symptoms
Cause
Solution
References

——————————————————————————–

Applies to:
Oracle Database – Enterprise Edition – Version 10.1.0.2 and later
Information in this document applies to any platform.
***Checked for relevance on 16-MAY-2012***

Symptoms
DataPump export (EXPDP) reports the following errors:

ORA-31626: job does not exist
ORA-31637: cannot create job SYS_EXPORT_FULL_01 for user SYSTEM
ORA-06512: at “SYS.DBMS_SYS_ERROR”, line 95
ORA-06512: at “SYS.KUPV$FT_INT”, line 600
ORA-39080: failed to create queues “KUPC$C_1_20070823095248” and “KUPC$S_1_20070
823095248” for Data Pump job
ORA-06512: at “SYS.DBMS_SYS_ERROR”, line 95
ORA-06512: at “SYS.KUPC$QUE_INT”, line 1580
ORA-04031: unable to allocate 4194344 bytes of shared memory (“streams pool”,”unknown object”,”streams pool”,”fixed allocation callback

Cause
The problem seems initially caused by having set the STREAMS_POOL_SIZE instance parameter to 0.
The first argument of the ORA-4031 error message also indicates a problem with the Streams pool.

The streams pool is used exclusively by Oracle Streams, see http://docs.oracle.com/cd/E11882_01/server.112/e25789/memory.htm#CNCPT1235
Also, Data Pump export and import operations initialize the Oracle Streams pool because these operations use buffered queues.
For information about the streams pool, refer to http://docs.oracle.com/cd/E11882_01/server.112/e10705/prep_rep.htm#STREP202

The size of the streams pool grows dynamically as required by Oracle Streams.
The (initial) size also depends on usage of ASMM, AMM or manual (minimum) settings.
That means that the parameter STREAMS_POOL_SIZE=0 is not the real root cause but the memory management cannot provide the automatic increase for the DataPump action at this time.
Setting STREAMS_POOL_SIZE>0 will guarantee a minimum size for the streams pool when using ASMM or AMM, hence avoiding the ORA-4031.

Solution
Set the STREAMS_POOL_SIZE instance parameter to at least 48MB to guarantuee a minimum size using:

SQL>connect / as sysdba

SQL> show parameter stream

NAME TYPE VALUE
———————————— ———– —————————–
streams_pool_size big integer 0

SQL>alter system set streams_pool_size=48m scope=both

Note:
For a large database and/or high workload using streams, the STREAMS_POOL_SIZE parameter may need to be higher (i.e. 150 MB) in order to avoid the ORA-4031 error.
References
NOTE:396940.1 – Troubleshooting and Diagnosing ORA-4031 Error [Video]

在设置streams_pool_size之后再来执行expdp导出正常导出
Export: Release 10.2.0.4.0 – 64bit Production on Monday, 17 February, 2014 11:01:52

Copyright (c) 2003, 2007, Oracle. All rights reserved.
;;;
Connected to: Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 – 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
Starting “SYSTEM”.”SYS_EXPORT_SCHEMA_01″: system/******** directory=dump_RLZY dumpfile=ybcwfull_140217_0946.dmp logfile=ybcwfull_140217_0946.log schemas=ZW
1001,ZW1002,ZW1003,ZW1004,ZW1005,ZW1006,ZW1101,ZW1102,ZW1103,ZW1104,ZW1105,ZW1106,ZW1201,ZW1202,ZW1203,ZW1204,ZW1205,ZW1206,ZW1301,ZW1302,ZW1303,ZW1304,ZW13
05,ZW1306,ZW2001,ZW2002,ZW2003,ZW2004,ZW2005,ZW2006,ZW3001,ZW3002,ZW3003,ZW3004,ZW3005,ZW3006,ZW4001,ZW4002,ZW4003,ZW4004,ZW4005,ZW4006,ZW5001,ZW5002,ZW5003
,ZW5004,ZW5005,ZW5006,ZW6001,ZW6002,ZW6003,ZW6004,ZW6005,ZW6006,ZW7001,ZW7002,ZW7003,ZW7004,ZW7005,ZW7006,ZW8001,ZW8002,ZW8003,ZW8004,ZW8005,ZW8006,ZW9001,Z
W9002,ZW9003,ZW9004,ZW9005,ZW9006,ZW9999
Estimate in progress using BLOCKS method…
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 997.3 MB
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/ROLE_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/SEQUENCE/SEQUENCE
Processing object type SCHEMA_EXPORT/TABLE/TABLE
Processing object type SCHEMA_EXPORT/TABLE/INDEX/INDEX
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/PACKAGE/PACKAGE_SPEC
Processing object type SCHEMA_EXPORT/FUNCTION/FUNCTION
Processing object type SCHEMA_EXPORT/PROCEDURE/PROCEDURE
Processing object type SCHEMA_EXPORT/PACKAGE/COMPILE_PACKAGE/PACKAGE_SPEC/ALTER_PACKAGE_SPEC
Processing object type SCHEMA_EXPORT/FUNCTION/ALTER_FUNCTION
Processing object type SCHEMA_EXPORT/PROCEDURE/ALTER_PROCEDURE
Processing object type SCHEMA_EXPORT/VIEW/VIEW
Processing object type SCHEMA_EXPORT/PACKAGE/PACKAGE_BODY
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/REF_CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/TRIGGER
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
……………….
. . exported “ZW9999″.”ZB_SKR” 0 KB 0 rows
. . exported “ZW9999″.”ZB_TKPZML” 0 KB 0 rows
. . exported “ZW9999″.”ZB_TKPZNR” 0 KB 0 rows
. . exported “ZW9999″.”ZB_WHSZ” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YEYKJH” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YEZFPZ” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YHBM” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YHZL” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YJZPML” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YJZPNR” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YKJH” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YKJHDR” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YKJHHQB” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YKJHML” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YKJHNR” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YSDWGX” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YSDZGS” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YSGLLX” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YSKMGX” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YSKMKJDY” 0 KB 0 rows
. . exported “ZW9999″.”ZB_YSKMKJGX” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZBBD” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZBHQB” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZBLY” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZCLX” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFBKD” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFFS” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFPZFJ” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFPZML” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFPZML_Y” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFPZNR” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFPZNR_Y” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFSQDFJ” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFSQDML” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFSQDNR” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZFSQHQB” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZJBFLX” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZLZLX” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZLZML” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZYXX” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZZB” 0 KB 0 rows
. . exported “ZW9999″.”ZB_ZZB_S” 0 KB 0 rows
Master table “SYSTEM”.”SYS_EXPORT_SCHEMA_03″ successfully loaded/unloaded
******************************************************************************
Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_03 is:
/yb_oradata/RLZYbak/dpdump/ybcwfull_140217_1102.dmp
Job “SYSTEM”.”SYS_EXPORT_SCHEMA_03″ successfully completed at 11:41:05

使用oradebug dump processstate 来诊断enq: TX – row lock contention

朋友的应用程序在年度结转时调用存储过程时hang住了.经过调试存储过程发现执行到下面的语句时被hang住.

UPDATE t_config_info
       SET last_do_time = systimestamp
     WHERE config_id = config_record.config_id;
    IF SQL%ROWCOUNT = 0 THEN
      RAISE error1;
    END IF;

解决这个问题的方法就是找到执行存储过程的会话,并用oradebug来dump进程信息.先执行下面的语句来找到执行存储过程会话对应的spid.

SQL>select p.spid
from v$session s,v$process p,v$sqlarea c
where s.username is not null and s.PADDR=p.ADDR and s.sql_id=c.sql_id
and s.sql_fulltext like'%UPDATE t_config_info%'
SPID
----------
14483524

得到的spid为14483524

在另一个会话中执行下面的语句

SQL> oradebug setospid 14483524
Statement processed.
SQL> oradebug unlimit
Statement processed.
SQL> oradebug dump processstate 10
Statement processed.
SQL> oradebug tracefile_name
/u01/app/oracle/diag/rdbms/hygeia/hygeia1/trace/hygeia2_ora_14483524.trc

从得到的跟踪文件中可以看到以下信息:

 SO: 0x700000758606100, type: 4, owner: 0x70000075c4e73e0, flag: INIT/-/-/0x00 if: 0x3 c: 0x3
     proc=0x70000075c4e73e0, name=session, file=ksu.h LINE:11467 ID:, pg=0
    (session) sid: 539 ser: 14973 trans: 0x700000753a47aa8, creator: 0x70000075c4e73e0
              flags: (0x45) USR/- flags_idl: (0x1) BSY/-/-/-/-/-
              flags2: (0x40008) -/-
              DID: , short-term DID:
              txn branch: 0x0
              oct: 6, prv: 0, sql: 0x7000005cced65d0, psql: 0x70000076ed6dc58, user: 123/ZWJH_MM
    ksuxds FALSE at location: 0
    service name: hygeia
    client details:
      O/S info: user: Administrator, term: LENOVO-JGXROLVS, ospid: 6608:6612
      machine: WORKGROUP\LENOVO-JGXROLVS program: plsqldev.exe
      application name: PL/SQL Developer, hash value=1190136663
      action name: SQL Window - New, hash value=3399691616
    Current Wait Stack:
     0: waiting for 'enq: TX - row lock contention'
        name|mode=0x54580006, usn< <16 | slot=0x15000b, sequence=0x362616
        wait_id=811 seq_num=812 snap_id=1
        wait times: snap=29.036136 sec, exc=29.036136 sec, total=29.036136 sec
        wait times: max=infinite, heur=29.036136 sec
        wait counts: calls=59 os=59
        in_wait=1 iflags=0x15a0
    There is at least one session blocking this session.
      Dumping 1 direct blocker(s):
        inst: 1, sid: 625, ser: 53645
      Dumping final blocker:
        inst: 1, sid: 625, ser: 53645

上面的inst: 1, sid: 625, ser: 53645可以知道造成阻塞的会话是1号实例中的会话sid,serial#为625,53645,被人为的kill掉了,然后后继多次执行这个存储过程当hang住后又kill掉了,找到阻塞的会话也知道问题产生的原因了问题也就解决了.

oracle 优化统计数据之直方图(histograms)

直方图是一种按数据出现的频率来进行分类存储的方法.在oracle中直方图是用来描述表中列数据的分布情况.每一个sql在被执行前都要经过优化这一步骤那么在优化器给出一个最优执行计划之优化器应该要知道sql语句中所引用的底层对象的详细信息.

直方图描述的对象包括列中不同值的数量和它们出现的频率.现在存储每一个不同值和它出现的频率是不可行的,特别是对于大表来说列中有上万个不同值,oracle使用直方图来存储关于列中数据分布的有用信息而且oracle的CBO使用直方图信息来计算出一个最优的执行计划.

CBO与直方图histograms
从一个行源中评估返回行数所占的比例这就是选择率,选择率在CBO的查询优化中起着重要作用.选择率的取值范围是0到1之间.粗略的讲,如果满足谓词条件的只有少量的行记录那么CBO将更喜欢使用索引扫描,如果谓词条件要从表中获取大量数据那么CBO将更喜欢使用全表扫描.比如下面的查询获取deptno等于10的所有雇员信息如果返回少量的记录查询将会更倾向于使用索引扫描:
select * from emp where deptno=10;

为了评估选择率(或者换句话说计算出最优执行计划),CBO会使用各种形式的统计信息,配置参数等.以表中列的角度来说,CBO会收集以下统计信息:
列中不同值的数量也就是NDV
列中的最小值/最大值
列中null值的数量
数据分布或直方图信息

在没有直方图时优化器使用基表中记录的列中不同值的数量,列中最小值/最大值和列中null值的数量来计算统计信息.使用这些信息优化器假设数据在列中的最小值和最大值之间是均匀分布的或者说列中每一个不同值的出现次数是相同的.
下面举列说明.创建一个测试表t1它有10000行记录,有两个列,列all_distinct包含不同值的范围从1到10000.列skew对于前10行记录的值从1到10,余下的9990行记录都是10000.

[oracle@jingyong ~]$ sqlplus / as sysdba

SQL*Plus: Release 11.2.0.1.0 Production on Sat Jan 4 06:05:14 2014

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

SQL> create table t1 as select rownum all_distinct,10000 skew from dual connect by level < =10000;

Table created.

SQL> update t1 set skew=all_distinct where rownum< =10;

10 rows updated.

SQL> commit;

Commit complete.

SQL> select skew,count(*) from t1 group by skew order by skew;

      SKEW   COUNT(*)
---------- ----------
         1          1
         2          1
         3          1
         4          1
         5          1
         6          1
         7          1
         8          1
         9          1
        10          1
     10000       9990

11 rows selected.

使用dbms_stata.gather_table_stats来收集统计信息是生成直方图是由参数method_opt来控制的method_opt参数的语法是由多个部分组成的.前两个部分是强制性的:
FOR ALL [INDEXED | HIDDEN] COLUMNS [size_clause]
FOR COLUMNS [size clause] column [size_clause] [,column…]

method_opt语法中的主要部分控制哪此列将收集列的统计信息(min,max,ndv,nulls).缺省是for all columns,它将会对表中所有的列(包括隐藏列)收集基本的列统计信息.

for all indexed columns将只对哪些包含索引的列进收集列统计信息.

for all hidden columns将只会对哪些虚拟列收集列统计信息.这意味着在对表收集统计时真实列是不会生成列统计信息的.这个值不能用于通常的统计信息收集.它只能用在当基表列的统计信息精确收集后在表中创建新的虚拟列.然后对新的虚拟列收集列统计信息时才使用它.

注意如果列不在统计信息收集列表中那么只会收集列的平均长度.

size用来指定直方图的桶数SIZE {integer | REPEAT | AUTO | SKEWONLY}
auto:基于列的使用信息(sys.col_usage$)和是否存在数据倾斜来收集直方图
integer:人为的指定创建直方图的桶数范围是1到254,如果size 1意味着不创建直方图
repeat:只会对已经存在直方图的列重新生成直方图.如果是一个分区表,repeat会确保对在全局级别存在直方图的列重新生成直方图.这是不被推荐的设置的.当前直方图的桶数将会作为重新生成直方图所使用的桶数的最大值.比如,当前直方图的桶数是5,那么生成的直方图最大桶数就是5,说的直白点就是刷新现有直方图的列上的统计信息.
skewonly:对任何数据分布出现倾斜列的自动创建直方图

现在来对表t1收集统计信息但不创建直方图

SQL> exec dbms_stats.gather_table_stats(user,'t1',method_opt=>'for all columns size 1');

PL/SQL procedure successfully completed.

SQL> select column_name,num_distinct,density from user_tab_col_statistics where
  2  table_name='T1';

COLUMN_NAME                    NUM_DISTINCT    DENSITY
------------------------------ ------------ ----------
ALL_DISTINCT                          10000      .0001
SKEW                                     11 .090909091

如果没有直方图,列的density统计信息代表了它的选择率它是通过去时1/num_distinct=1/11=0.09090901来计算出来的.在有直方图的情况下,density的计算依赖于直方图的类型和oracle的版本.density值的范围是0到1之间.当查询使用这个列作谓词条件时优化器将会使用这个列的density统计信息来评估将要返回的行数.所以 cardinality(基数)=selectivity(选择率)* number of rows(表的行数)

下面来检查一下在谓词条件中列的数据分布存在倾斜而没有直方图的情况下其基数评估的情况:

SQL> explain plan for select * from t1 where skew=1;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |   909 |  6363 |     7  (15)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |   909 |  6363 |     7  (15)| 00:00:01 |
--------------------------------------------------------------------------

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

   1 - filter("SKEW"=1)


SQL>  explain plan for select * from t1 where skew=10000;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |   909 |  6363 |     7  (15)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |   909 |  6363 |     7  (15)| 00:00:01 |
--------------------------------------------------------------------------

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

   1 - filter("SKEW"=10000)

因为oracle假设列skew中的数据是均匀分布的所以基数评估cardinality=density*num_rows=0.09090901*10000=909.09,四舍五入就是909行.但是我们知道skew=1的记录只有1行而skew=10000的记录有9990行.这种假设必然导致错误的执行计划.例如,如果我们在列skew上创建一个B树索引,oracle将使用对谓词skew=10000行使用索引扫描并返回909行记录.

SQL> create index skew_idx on t1(skew);

Index created.

SQL> exec dbms_stats.gather_index_stats(user,'skew_idx');

PL/SQL procedure successfully completed.

SQL> explain plan for select * from t1 where skew=10000;

Explained.

SQL> select  * from table(dbms_xplan.display);
Plan hash value: 3994350891
----------------------------------------------------------------------------------------
| Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |          |   909 |  6363 |     4   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1       |   909 |  6363 |     4   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | SKEW_IDX |   909 |       |     2   (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

   2 - access("SKEW"=10000)

因为我们知道没有给出关于数据分布的额外信息,CBO假设列中的数据在最小值和最大值之间是均匀分布的所以选择了错误的执行计划.

oracle直方图
一旦对列创建直方图后,它将告诉CBO列中数据出现的频率.所以在上面的例子中如果对列skew创建直方图它将告诉优化顺skew=1的值只出现一次,skew=10000的值出现了9990次.因此它能让优化器选择最优的执行计划.

在oracle中有两种类型的直方图.第一种是oracle会选择存储列中每一个不同值以及其出现的频率,称这种为宽度平衡直方图或频率直方图.这对于列有少量的不同值来说是有效和可能的方式.然而当列有大量不同值时要存储每一个不同值以及其出现的频率是不可能的.当然在无限资源(存储空间和计算能力和解析时间)的情况下,可以在任何情况下对每一个不同值存储其出现的频率来对优化器提供最终的信息,但是在真实的环境中这是不可能的.所以oracle使用高度平衡直方图来存储这样的数据.oracle会根据列中不同值的数量来自动判断所要创建直方图的类型,不同类型的直方图所描述的信息是不同的.

频率直方图(frequence histograms)
频率直方图列中的不同值被划到相同数量的桶中.每一个桶中存储的都是相同的值,也就是说频率直方图的桶数等于列的不同值的个数.buckets=ndv

下面的图表代表了列skew的数据分布情况.从图表中可以看出以下信息:
在x轴有11个桶,每一个桶代表了一个不同的值
Y轴显示了每一个不同值出现的频率.skew的1到10的频率是1,值10000的频率是9990
通过查看这样的信息可以很容易的说出一个特定值出现的频率

下面来对列skew创建一个频率直方图并查看数据是怎样存储在数据字典视图中的.现在对参数method_opt使用’for column column_name size n’来创建指定桶数的直方图.

SQL> exec dbms_stats.gather_table_stats(user,'t1',method_opt=>'for columns skew size 11');

PL/SQL procedure successfully completed.


SQL> select column_name,endpoint_number,endpoint_value from user_tab_histograms where
  2    table_name='T1' and column_name='SKEW';

COLUMN_NAME  ENDPOINT_NUMBER ENDPOINT_VALUE
------------ --------------- --------------
SKEW                       1              1
SKEW                       2              2
SKEW                       3              3
SKEW                       4              4
SKEW                       5              5
SKEW                       6              6
SKEW                       7              7
SKEW                       8              8
SKEW                       9              9
SKEW                      10             10
SKEW                   10000          10000

第一个语句对列skew创建了有11个桶的直方图,因为我们知道列skew有11个不同的值.第二个语句显示了存储在数据字典视图中的直方图数据.直方图中存储的信息依赖于直方图的桶数小于列不同值的个数或者相等会有不同的解释,也就是说直方图中存储的信息依赖于直方图的类型会有不同的解释.下面解释频率直方图所代表的信息.

Endpoint_value显示的是真实的列值,endpoint_number显示的是累积的行数或者是累积的频率.为了计算一个特定列值的频率需使用与它相关的endpoint_number值减去它之前的累积值.
例如,对于endpoint_value为5的值,它的endpoint_number为5,之前的endpoint_number为4,因上skew=5的记录只有5-4=1行.类似的对于endpoint_value为10000的值它的endpoint_number为10000它之前的endpoint_number为10,所以skew=10000的记录有10000=10=9990行.

使用下面的sql来解释说明存储在数据字典中的直方图信息:

SQL> select endpoint_value as column_value,
  2  endpoint_number as cummulative_frequency,
  3  endpoint_number - lag(endpoint_number,1,0) over (order by endpoint_number) as frequency
  4  from user_tab_histograms
  5  where table_name ='T1' and column_name='SKEW';

COLUMN_VALUE CUMMULATIVE_FREQUENCY  FREQUENCY
------------ --------------------- ----------
           1                     1          1
           2                     2          1
           3                     3          1
           4                     4          1
           5                     5          1
           6                     6          1
           7                     7          1
           8                     8          1
           9                     9          1
          10                    10          1
       10000                 10000       9990

存储总的或累积频率来代替单个频率在范围扫描时是特别有用的对于象where skew< =10这样的谓词基数就现成的. 现在因为我们对更skew创建了直方图再来查看之前的查询有什么不同:

SQL> select column_name,num_distinct,density,histogram from user_tab_col_statistics where table_name=’T1′;

COLUMN_NAME                    NUM_DISTINCT    DENSITY HISTOGRAM
—————————— ———— ———- —————
ALL_DISTINCT                          10000      .0001 NONE
SKEW                                     11     .00005 FREQUENCY


SQL> explain plan for select * from t1 where skew=10000;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
—————————————————————————
Plan hash value: 3617692013

————————————————————————–
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
————————————————————————–
|   0 | SELECT STATEMENT  |      |  9990 | 69930 |     7  (15)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |  9990 | 69930 |     7  (15)| 00:00:01 |
————————————————————————–

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

   1 – filter(“SKEW”=10000)

13 rows selected.



SQL> explain plan for select * from t1 where skew=1;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
—————————————————————————————-
Plan hash value: 3994350891

—————————————————————————————-
| Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
—————————————————————————————-
|   0 | SELECT STATEMENT            |          |     1 |     7 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T1       |     1 |     7 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | SKEW_IDX |     1 |       |     1   (0)| 00:00:01 |
—————————————————————————————-

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

   2 – access(“SKEW”=1)

14 rows selected.

现在优化器对于谓词skew=10000选择了全表扫描且能精确计算出它的基数9990.注意现在skew列的density是变成了0.00005也就是1/(2*num_rows)或者0.5/num_rows.

高度平衡直方图(height-balanced histograms)
在频率直方图中oracle给每一个不同值分配一个桶,然而桶的最大个数是254,因此如果表中的列有大量的不同值(超过254),将会创建一个高度平衡的直方图.

在高度平衡直方图中,因为我们的不同值超过了桶的个数,因此oracle首先分对列数据进行排序然后将数据集按桶数进行分类且除了最后一桶可能包含的数据比其它的桶少以外,所有其它的桶包含相同数量的值(这就是为什么叫高度平等直方图的原因).

这是有一个单独的语句用来创建高度平衡直方图.当请求的桶数少于列中不同值的个数时,oracle就会创建一个高度平衡直方图且这意味着endpoint_value和endpoint_number是不相同的.为了解释这种类型直方图的信息先看一个列有23个值且有9个不同值的例子.假设我们指定直方图的桶数是5,下面的图表显示了这些数据是如何存储在直方图中的:
1 
基于上面的图表可以得出以下信息:
直方图的桶数比列中的不同值的个数小
因为我们指定了直方图的桶数是5,所以整个数据集除了最后一个桶(在这里只有3个值)其它按相同的大小分配到每一个桶中.
每一个桶中的endpoints和第一个桶中的first point被标记因为它们有特殊意义.
数据3被标记为红色,它是一种特殊情况它的endpoint出现在多个桶中.
下面的图表是直方图的另一种显示方式:
2 

使用5个桶且列有23个值这意味着除了最后一个桶只有3个值以外其它每一个桶都有5个值.实际上这是oracle在数据字典视图中存储高度平衡直方图信息的方式.因为bucket 1和2都使用3作为一个endpoint,oracle为了节省空间将不会存储bucket 1.所以当桶被合并时只会存储单个条目.
3 

下面我们来对列skew创建一个高度平衡直方图,这一次让桶数小于列的不同值的个数11:
SQL> select  column_name,endpoint_number,endpoint_value from
  2    user_tab_histograms where table_name='T1' and column_name='SKEW';

COLUMN_NAME     ENDPOINT_NUMBER ENDPOINT_VALUE
--------------- --------------  --------------
SKEW                         0               1
SKEW                         5           10000

这里buckets 1到5都是用10000作为它的endpoint所以bucket 1到4为了节省空间没有被存储.下面的查询能用来显示桶数和它的endpoinit值

SQL> SELECT bucket_number, max(skew) AS endpoint_value
  2   FROM (
  3   SELECT skew, ntile(5) OVER (ORDER BY skew) AS bucket_number
  4   FROM t1)
  5   GROUP BY bucket_number
  6   ORDER BY bucket_number;

BUCKET_NUMBER ENDPOINT_VALUE
------------- --------------
            1          10000
            2          10000
            3          10000
            4          10000
            5          10000

这里ntile(5)是一个分析函数,它将一个有序的数据集划分到5个桶中.

所以简而言之,在高度平衡直方图中,数据被划分到不同的桶中除了最后一个桶每一个桶包含相同的数据.每一个桶中的最大值被记录为endpoint_value而第一个桶中的最小值也被记录(bucket 0).endpoint_number代表桶数.一旦数据被记录到桶中将会识别为2种类型的数据:
Non popular values和popular values.

Popular values是哪些作为endpoint value出现多次的值.例如在前面的例子中3是一个popular值,在上面的例子中skew 10000是一个popular value.non popular value是哪些没有作为endpoint values出现或者只作为endpoint values出现一次的值.popular value和non popular value不是固定的它依赖于直方图桶的大小,改变桶的大小会出现不同的popular值.

小结:
列中不同值的个数小于直方图的桶数:当不同值的个数小于桶数时,endpoint_value列包含的是不同值本身,endpoint_number列包含是小于列值的累积行数.(频率直方图)

列中不同值的个数大于直方图的桶数:当不同值的个数大于桶数时,endpoint_number包含的是bucekt id且endpoint_value是显示的每一个桶中的最大值.bucket 0是一个特殊它显示的是列中的最小值(高度平衡直方图).

基于CBO优化器谓词选择率的计算方法

选择率selectivity是Oracle的CBO中比较重要的一个概念。和selectivity经常一起提到的一个概念是cardinality,其实这两个概念的本质上是一样的。selectivity是指一个SQL操作的得出结果集占原来结果集的百分比,而cardinality就是指一个SQL操作的得出结果集的行数,CBO是通过selectivity来计算cardinality的,也就是说cardinality=selectivity*原结果集行数。
创建一个测试表

create table t1 as
select
trunc(dbms_random.value(1,13)) month_no
from all_objects
where rownum< =1200;


sys@JINGYONG> select high_value,low_value,num_distinct,num_nulls,density
  2  from dba_tab_col_statistics where owner='SYS' and table_name='t1'
  3  and column_name='MONTH_NO';

HIGH_VALUE  LOW_VALUE    NUM_DISTINCT  NUM_NULLS    DENSITY
C10D        C102         12            0            .083333333

sys@JINGYONG> select blocks,num_rows from user_tables where table_name='t1
';

    BLOCKS   NUM_ROWS
---------- ----------
         2       1200

sys@JINGYONG> show parameter db_file_multiblock_read_count

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_file_multiblock_read_count        integer     40

sys@JINGYONG> show parameter db_block_size

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_block_size                        integer     8192

选择率=所需的空间 除以 总可用空间
通过计算(user_tab_col_statistics.high_value – user_tab_col_statistics.low_value)可以得出,上面的例子中整个区间大小为11,当计算得到的是11,那么我们就知道其中肯定有错误.因为我们知道,上例中有12个离散的月份,但优化器采用的算法却将这些数据看作连续的变化数据,并且总区间大小为11.
单列谓词的选择率
示例1
select count(*) from t1 where month_no>8

sys@JINGYONG> select count(*) from t1 where month_no>8;

  COUNT(*)
----------
       396

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  6u62fruy6276s, child number 0
-------------------------------------
select count(*) from t1 where month_no>8

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL|       T1 |   436 |  1308 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter("MONTH_NO">8)

当where month_no>8:这是一个无边界(一侧没有边界)的开(不包含8)区间.
选择率=(high_value-limit)/(high_value-low_value)=(12-8)/(12-1)=4/11
基数=1200*4/11=436.363636

成本是2
全表扫描的成本计算公式:
Cost = (
#SRds * sreadtim +
#MRds * mreadtim +
#CPUCycles / cpuspeed
) / sreadtim
where
#SRDs – number of single block reads
#MRDs – number of multi block reads
#CPUCycles – number of CPU Cycles
sreadtim – single block read time
mreadtim – multi block read time
cpuspeed – CPU cycles per second

成本指的是花费在块读取上的时间,加上花在多块读取上的时间,再加上所需的cpu处理的时间,然后将总和除以单块读取所花费的 时间,也就是说,成本是语句的预计执行时间的总和,以单块读取时间单元的形式来表示.
如果oracle收集了操作系统统计信息,那么CBO采用工作量统计模式计算代价
如果oracle没有收集操作系统统计信息,那么CBO采用非工作量统计模式计算代价我们现在处于“非工作量统计模式”

sys@JINGYONG> select pname,pval1 from aux_stats$;

PNAME                               PVAL1
------------------------------ ----------
STATUS
DSTART
DSTOP
FLAGS                                   0
CPUSPEEDNW                       1149.062
IOSEEKTIM                              10
IOTFRSPEED                           4096
SREADTIM
MREADTIM
CPUSPEED
MBRC
MAXTHR
SLAVETHR

#SRds=0,因为是全表扫描,单块读为0,全都使用的是多块读
#MRds=表的块数/多块读参数=2/40=0.05
mreadtim=ioseektim+(db_file_multiblock_count*db_block_size/iotftspeed)=10+(40*8192/4096)=90
sreadtim=ioseektim+(db_block_size/iotfrspeed)=10+(8192/4096)=12
CPUCycles 等于 PLAN_TABLE里面的CPU_COST

sys@JINGYONG> explain plan for select count(*) from t1 where month_no>8;

已解释。

sys@JINGYONG> select cpu_cost from plan_table;

  CPU_COST
----------
    254243

cpuspeed 等于 CPUSPEEDNW= 1149.062
COST=(0*12/12)+(0.05*90/12)+(254243/1149.062/12/1000)(毫秒换算成秒)=
0+0.375+0.01843=0.39343
0.375是IO成本
0.01843是CPU成本
手工计算出来的COST取最接近的整数等于1和我们看到的2有差别
这是由于隐含参数_table_scan_cost_plus_one参数造成的

SQL> SELECT x.ksppinm NAME, y.ksppstvl VALUE, x.ksppdesc describ FROM x$ksppi x,x$ksppcv y
  2    WHERE x.inst_id = USERENV ('Instance')
  3     AND y.inst_id = USERENV ('Instance')
  4     AND x.indx = y.indx
  5     AND x.ksppinm LIKE '%_table_scan_cost_plus_one%';

NAME                                                                             VALUE                                                                              DESCRIB
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
 --------------------------------------------------------------------------------
_table_scan_cost_plus_one                                                        TRUE                                                                             bump estimated full table scan and index ffs cost by one

根据该参数的描述,在table full scan和index fast full scan的时候会将cost+1 即 1+1=2;

示例2
select count(*) from t1 where month_no>=8
这是一个无边界的闭(包含8)区间,因此需要对算法进行调整.具体调整过程为必须包括闭合值(此处即为8)所在的行–也就是要增 加一个1/num_distinct(要注意的是oracle8i使用的是density而不是1/num_distinct,但是,如果没有获取统计信息或者没有直方 图可用,那么无须注意这一差别)
选择率=(high_value-limit)/(high_value-low_value)+1/num_distinct=4/11+1/12
基数=1200*(4/11+1/12)=536.363636

sys@JINGYONG> show parameter db_file_multiblock_read_count

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_file_multiblock_read_count        integer     40

sys@JINGYONG> select count(*) from t1 where month_no>=8;

  COUNT(*)
----------
       497

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  g748ttx5rv2p1, child number 0
-------------------------------------
select count(*) from t1 where month_no>=8

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   536 |  1608 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter("MONTH_NO">=8)

示例3
select count(*) from t1 where month_no<8
选择率=(limit-low_value)/(high_value-low_value)=(8-1)/(12-1)=7/11
基数=1200*7/11=763.636364

sys@JINGYONG> select count(*) from t1 where month_no<8;

  COUNT(*)
----------
       703

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------------------------
SQL_ID  cpvw8yxstbtng, child number 0
-------------------------------------
select count(*) from t1 where month_no<8

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   764 |  2292 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter("MONTH_NO"<8)

示例4
select count(*) from t1 where month_no< =8 选择率=(limit-low_value)/(high_value-low_value)+1/num_distinct=(8-1)/(12-1)+1/12 基数=1200*(7/11+1/12)=863.636364

sys@JINGYONG> select count(*) from t1 where month_no< =8;

  COUNT(*)
----------
       804

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
——————————————————————————–

SQL_ID  buhw0y52jy3nr, child number 0
————————————-
select count(*) from t1 where month_no< =8

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   864 |  2592 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter("MONTH_NO"<=8)

示例5
select count(*) from t1 where month_no between 6 and 9
两边都是有界的闭区间它其实与
select count(*) from t1 where month_no>=6 and month_no< =9是等价的.它们给出了两个闭合值.因此要对算法进行两 次调整. 选择率=(high_limit-low_limit)/(high_value-low_value)+1/num_distinct+1/num_distinct=(9-6)/(12-1)+1/12+1/12 基数=1200*(3/11+1/6)=527.272727

sys@JINGYONG> select count(*) from t1 where month_no between 6 and 9;

  COUNT(*)
———-
       421

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
——————————————————————————–
SQL_ID  83ud81y133kxm, child number 0
————————————-
select count(*) from t1 where month_no between 6 and 9

Plan hash value: 3337892515

——————————————————————————-
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
——————————————————————————-
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   527 |  1581 |     2   (0)| 00:00:01 |
——————————————————————————-

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



sys@JINGYONG> select count(*) from t1 where month_no>=6 and month_no< =9;

  COUNT(*)
----------
       421

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
——————————————————————————–
SQL_ID  74gjsbjjdv97k, child number 0
————————————-
select count(*) from t1 where month_no>=6 and month_no< =9

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   527 |  1581 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter(("MONTH_NO">=6 AND “MONTH_NO”< =9))

示例7
select count(*) from t1 where month_no>=6 and month_no<9
选择率=(high_limit-low_limit)/(high_value-low_value)+1/num_distinct=(9-6)/(12-1)+1/12
基数=1200*(3/11+1/12)=427.272727

sys@JINGYONG> select count(*) from t1 where month_no>=6 and month_no<9;

  COUNT(*)
----------
       328

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

SQL_ID  5w0n5237vpngb, child number 0
-------------------------------------
select count(*) from t1 where month_no>=6 and month_no<9

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   427 |  1281 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter(("MONTH_NO">=6 AND "MONTH_NO"<9))


示例8
select count(*) from t1 where month_no>6 and month_no< =9 选择率=(high_limit-low_limit)/(high_value-low_value)+1/num_distinct=(9-6)/(12-1)+1/12 基数=1200*(3/11+1/12)=427.272727

sys@JINGYONG> select count(*) from t1 where month_no>6 and month_no< =9;

  COUNT(*)
----------
       303

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
——————————————————————————–

SQL_ID  bbd2wxjratndg, child number 0
————————————-
select count(*) from t1 where month_no>6 and month_no< =9

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   427 |  1281 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter(("MONTH_NO">6 AND “MONTH_NO”< =9))


示例9
select count(*) from t1 where month_no>6 and month_no<9
选择率=(high_limit-low_limit)/(high_value-low_value)=(9-6)/(12-1)
基数=1200*(3/11)=327.272727

sys@JINGYONG> select count(*) from t1 where month_no>6 and month_no<9;

  COUNT(*)
----------
       210

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

SQL_ID  6axnk33swbtpw, child number 0
-------------------------------------
select count(*) from t1 where month_no>6 and month_no<9

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   327 |   981 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter(("MONTH_NO">6 AND "MONTH_NO"<9))

对于使用绑定变量的谓词优化器就无法知道任何变量的值也不知道绑定变量的类型,因此,这种情况下优化器
就会使用一个固定的选择率来产生执行计划.例如:

sys@JINGYONG> explain plan for select count(*) from t1 where month_no>:b1;


已解释。

sys@JINGYONG> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |     1 |     3 |     2   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   101 |   303 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter("MONTH_NO">TO_NUMBER(:B1))



sys@JINGYONG> explain plan for select count(*) from t1 where month_no between :b1 and :b2;

已解释。

sys@JINGYONG> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 57893822

--------------------------------------------------------------------------------

| Id  | Operation           | Name     | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT    |          |     1 |     3 |     2   (0)| 00:00:01 |

|   1 |  SORT AGGREGATE     |          |     1 |     3 |            |          |

|*  2 |   FILTER            |          |       |       |            |          |

|*  3 |    TABLE ACCESS FULL| T1 |   101 |   303 |     2   (0)| 00:00:01 |

--------------------------------------------------------------------------------


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

   2 - filter(TO_NUMBER(:B1)< =TO_NUMBER(:B2))
   3 - filter("MONTH_NO">=TO_NUMBER(:B1) AND "MONTH_NO"< =TO_NUMBER(:B2))



sys@JINGYONG> explain plan for select count(*) from t1 where month_no>=:b1
;

已解释。

sys@JINGYONG> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |     1 |     3 |     2   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   101 |   303 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter("MONTH_NO">=TO_NUMBER(:B1))

已选择14行。

sys@JINGYONG> explain plan for select count(*) from t1 where month_no<:b1 ;


已解释。

sys@JINGYONG> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |     1 |     3 |     2   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   101 |   303 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter("MONTH_NO" explain plan for select count(*) from t1 where month_no< =:b1
;

已解释。

sys@JINGYONG> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |     1 |     3 |     2   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   101 |   303 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter("MONTH_NO"< =TO_NUMBER(:B1))

已选择14行。

sys@JINGYONG> explain plan for select count(*) from t1 where month_no>=:b1
 and month_no< =:b2;

已解释。

sys@JINGYONG> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 57893822

--------------------------------------------------------------------------------

| Id  | Operation           | Name     | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT    |          |     1 |     3 |     2   (0)| 00:00:01 |

|   1 |  SORT AGGREGATE     |          |     1 |     3 |            |          |

|*  2 |   FILTER            |          |       |       |            |          |

|*  3 |    TABLE ACCESS FULL| T1 |   101 |   303 |     2   (0)| 00:00:01 |

--------------------------------------------------------------------------------


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

   2 - filter(TO_NUMBER(:B1)< =TO_NUMBER(:B2))
   3 - filter("MONTH_NO">=TO_NUMBER(:B1) AND "MONTH_NO"< =TO_NUMBER(:B2))

已选择16行。



sys@JINGYONG> explain plan for select count(*) from t1 where month_no>=:b1
 and month_no<:b2 ;

已解释。

sys@JINGYONG> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 57893822

--------------------------------------------------------------------------------

| Id  | Operation           | Name     | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT    |          |     1 |     3 |     2   (0)| 00:00:01 |

|   1 |  SORT AGGREGATE     |          |     1 |     3 |            |          |

|*  2 |   FILTER            |          |       |       |            |          |

|*  3 |    TABLE ACCESS FULL| T1 |   101 |   303 |     2   (0)| 00:00:01 |

--------------------------------------------------------------------------------


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

   2 - filter(TO_NUMBER(:B1)=TO_NUMBER(:B1) AND "MONTH_NO" explain plan for select count(*) from t1 where month_no>:b1
and month_no<:b2 ;

已解释。

sys@JINGYONG> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 57893822

--------------------------------------------------------------------------------

| Id  | Operation           | Name     | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT    |          |     1 |     3 |     2   (0)| 00:00:01 |

|   1 |  SORT AGGREGATE     |          |     1 |     3 |            |          |

|*  2 |   FILTER            |          |       |       |            |          |

|*  3 |    TABLE ACCESS FULL| T1 |   101 |   303 |     2   (0)| 00:00:01 |

--------------------------------------------------------------------------------


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

   2 - filter(TO_NUMBER(:B1)TO_NUMBER(:B1) AND "MONTH_NO" explain plan for select count(*) from t1 where month_no>:b1
and month_no< =:b2;

已解释。

sys@JINGYONG> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 57893822

--------------------------------------------------------------------------------

| Id  | Operation           | Name     | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT    |          |     1 |     3 |     2   (0)| 00:00:01 |

|   1 |  SORT AGGREGATE     |          |     1 |     3 |            |          |

|*  2 |   FILTER            |          |       |       |            |          |

|*  3 |    TABLE ACCESS FULL| T1 |   101 |   303 |     2   (0)| 00:00:01 |

--------------------------------------------------------------------------------


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

   2 - filter(TO_NUMBER(:B1)TO_NUMBER(:B1) AND "MONTH_NO"< =TO_NUMBER(:B2))

对于上面所有使用绑定变量的查询其评估的基数都是101使用固定选择率

当查询超出列的最低/最高界限测试的结果如下.其评估的基数是100使用固定选择率

sys@JINGYONG> select count(*) from t1 where month_no between 14 and 17;

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

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  8baayn49yujkh, child number 0
-------------------------------------
select count(*) from t1 where month_no between 14 and 17

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   100 |   300 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter(("MONTH_NO">=14 AND "MONTH_NO"< =17))


已选择19行。

sys@JINGYONG> select count(*) from t1 where month_no between 18 and 21;

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

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  9agzhp783caa1, child number 0
-------------------------------------
select count(*) from t1 where month_no between 18 and 21

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   100 |   300 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter(("MONTH_NO">=18 AND "MONTH_NO"< =21))


已选择19行。

sys@JINGYONG> select count(*) from t1 where month_no between 24 and 27;

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

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  dksv39k0vx5sh, child number 0
-------------------------------------
select count(*) from t1 where month_no between 24 and 27

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   100 |   300 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter(("MONTH_NO">=24 AND "MONTH_NO"< =27))


已选择19行。

sys@JINGYONG> select count(*) from t1 where month_no between -11 and 0;

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

sys@JINGYONG> select * from table(dbms_xplan.display_cursor());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  drdqd21q24hzy, child number 0
-------------------------------------
select count(*) from t1 where month_no between -11 and 0

Plan hash value: 3337892515

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE    |          |     1 |     3 |            |          |
|*  2 |   TABLE ACCESS FULL| T1 |   100 |   300 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   2 - filter(("MONTH_NO"< =0 AND "MONTH_NO">=(-11)))


已选择19行。

双谓词选择率
为了能够计算通用的联合谓词选择率,需要用到以下3种基本公式,它们是以选择率的形式给出的,而不是以基数的形式给出的.
(谓词1 and 谓词2)的选择率=谓词1的选择率+谓词2的选择率
(谓词1 or 谓词2)的选择率=谓词1的选择率+谓词2的选择率-(谓词1 and 谓词2)的选择率
(not 谓词1)的选择率=1-谓词1的选择率,绑定变量不能使用这个公式进行计算

下面是一组概率论方面的公式
p(a and b)=p(a)*p(b)
p(a or b)=p(a)+p(b)-p(a and b)=p(a)+p(b)-p(a)*p(b)
p(not a)=1-p(a)
上面的联合谓词选择率与概率论公式是等价的.

首先,我们来细致研究如下的where子句:
select count(*) from T1 where month_no>8 or month_no< =8; 利用简单的选择率计算公式required range/total range可以得出谓词1的选择率为(12-8)/(12-1)=4/11=0.363636 同样的.谓词2的选择率为(8-1)/(12-1)+1/12=7/11+1/12=0.719696 (p1 or p2)的选择率计算公式为p1的选择率+p2的选择率-(p1 and p2)的选择率,因此,联合的选择率为 0.363636+0.719696-(0.363636*0.719696)=0.8216 将这个选择率乘以1200行,进行四舍五入后,得到结果后为986,很明显,这个结果和人的直观思维得到的结果不一样. 当我们将多个谓词应用到某个表时,需要确定测试的列之间是否存在依赖关系.如果存在依赖关系,优化器得到的选择率将是错误 的,基数也就是错误的,因此也就无法得到合适的执行计划. 小结: 为了估计一组谓词返回数据的行数,优化器首先计算选择率(返回数据行数的分数),然后将其与输入行数相乘.对于单个列上的单个谓词,优化器将利用不同值的数目或density作为计算谓词选择率的基础.对于单个列上基于的区间谓词,优 化器采用分数required range/total available range进行一些端点值调整的方式来计算谓词选择率.对于包含绑定变量的区间谓词来说,优化器使用硬编码常量作为选择率优化器通过使用类似于计算独立事件的联合概率方式的公式来计算联合谓词的选择率.如果列中包含的数据集并不是相互独立的 话,在选择率的计算方面将会出现误差,从而导致基数出现误差.

深入理解oracle优化器统计数据(Optimizer Statistics)

首先来介绍oracle数据库使用基于规则优化器(RBO)来决定如何执行一个sql语句.基于规则优化器顾名思义,它是遵循一组规则来判断一个sql语句的执行计划.这组规则是有排名的如果有两种可能的规则应该应用于一个sql语句,那么排名较低的规则会被使用

在oracle 7中,引入了基于成本的优化器使用优化器的功能得以增强包括并行执行,分区,还会考虑用户真实数据的内容和分布情况.基于成本的优化器会检查一个sql语句的所有执行计划并选择一个成本值最低的,这里的成本代表一个指定执行计划所要消耗的资源使用情况.一个执行计划成本值越低该执行计划越有效.为了让基于成本的优化器精确的判断一个执行计划的成本它必需有描述该语句所访问的所有对象(表和索引)的信息和描述运行这个语句的系统信息.这些需要的信息通常被称为优化器统计数据.理解和管理优化器统计数据是优化sql执行的关键.知道何时以及及时的怎样收集统计数据对于维护一个稳定的性能来说很关键.优化器统计数据包括以下方面的信息:
什么是优化器统计数据
收集统计数据
管理统计数据
其它类型的统计数据

什么是优化器统计数据
优化器统计数据是一组描述数据库和数据库中对象信息的集合.这些统计数据在优化器给每一个sql语句选择一个最优的执行计划时会被使用.统计数据存储在数据字典中且它们能通过数据字典视图比如象user_tab_statistics来进行访问.优化器统计数据与v$视图来查看的性能统计数据不同.v$视图中的信息是系统状态和执行sql工作负载的信息.
1

表和列统计数据
表统计数据包括了表中的行记录数,表用来存储这些数据的数据块数,表中的平均行长度等信息.优化器使用这些信息并结合其它的统计数据来计算执行计划中各个操作的成本并评估每一个操作将返回的行数.例如,一个表扫描的成本是使用表所使用的数据块数和参数db_file_multiblock_read_count来计算出来的.可以查询user_tab_statistics视图来查看表的统计数据.

列统计数据包括一个列中的不同值的个数(NDV)还有在这个列中的最小值/最大值.可能查询user_tab_col_statistics视图来查看这列的统计数据.优化器使用列统计数据并结合表的统计数据(行数)来评估一个sql操作将要返回的行数.例如一个表有100行记录且对一个有10个不同值的列使用等号谓词评估,那么优化器会假设统一的数据分布,那么评估的基数等于表的记录数除以这个列不同值的个数:100/10=10.
2

额外的列统计数据
基本表和列统计数据没有提供一种机制来告诉优化器关于表中或列中的数据的特性.例如,这些统计数据不能告诉优化器表中的列中的数据是否有倾斜或者列之间是否存在关联.数据特性的信息可以通过扩展基本的统计数据象,直方图,列组和表达式统计数据提供给优化器.

直方图
直方图告诉优化器关于列中的数据分布情况.缺省情况(没有直方图),优化器会假设一个列中的不同值会均匀分布的.同上所述,优化器评估一个等号谓词的基数是通过这个表中的行数除以这个等号谓词列中不同值的个数得到的.如果数据分布不是均匀的(比如数据倾斜),那么这样的基数评估就会出错.为了精确的反映非均匀的数据分布就需要对列创建直方图.直方图的存在改变了优化器评估基数的公式且会让优化器生成一个更精确的执行计划.

Oracle基于列的使用信息(SYS.COL_USAGE$)和数据的倾斜情况能自动判断这个列是否需要直方图.例如只在一个等号谓词中看到的唯一性列oracle是不会对这个唯一性列自动创建直方图.

有两种类型的直方图,频率直方图和高度平衡直方图.oracle会基于列中的不同值的个数来决定创建直方图的类型.

频率直方图
当列中的不同值的个数据小于254时会创建频率直方图.oracle使用下面的步骤来创建频率直方图:
1. 让我们假设oracle正对promotions表的promo_category_id创建一个频率直方图.第一步从promotions表中通过对promo_category_id排序来查询promo_category_id
2. 每一个promo_category_id被指派到它所属的直方图bucket中
3

在这个步骤中可能直方图的bucket数超过254,因此拥有相同值的bucket会被压缩到使用这个值的最高bucket中.在这种情况下,bucket 2到 115会被压缩到bucket 115,且bucket 484到503会被压缩到bucket 503直到总的bucket数仍然保持与列中不同值的个数相等.注意上面的步骤是出于演示.dbms_stats包对直接构造压缩直方图进行了优化.
4

优化器现在使用频率直方图可以精确的判断谓词promo_category_id的基数.例如,对于谓词promo_category_id=10,优化器首先需要判断在直方图中10作为end point的bucket的数量.通过找到endpoint为10的bucket 503,然后减去前面的bucket数,bucket 483,503-483=20.然后基数评估将使用下面的公式进行计算(number of bucket endpoints/ total number of bucket) * num_rows,20/503*503,所以在promotoins表中promo_category_id=10的记录有20行.

当列中的不同值的数理超过254时就会创建高度平衡直方图.在高度平衡直方图中,列值被划分到bucket中所以每一个bucket可能包含相同数量的行.oracle使用下面的步骤来创建一个高度平衡直方图.
1. 我们假设oracle将要对customers表中的cust_city_id列创建一个高度平衡直方图因cust_city_id列的不同值的数量超过了254.与频率直方图类似,第一步是执行ordered by cust_city_id子句来查询customers表中的cust_city_id.

2. 在customers表中有55500行直方图中最多有254个bucket.为了让每一个bucket中有一个相等的行数,oracle必须在每一个bucket中存入219行.第219行的cust_city_id将成为第一个bucket的endpoint在这种情况下是51043.第438行的cust_city_id将成为第二个bucket的endpoint并且一直到所有254buckets被填满为止.
5

3. 一旦bucket创建完成后oracle会检查是否第一个bucket的endpoint值是否是cust_city_id列中的最小值,如果不是,一个”zero”bucket会被添加到直方图中而且cust_city_id列中的最小值作为它的endpoint.
6

4. 与频率直方图一样最后的步骤是压缩高度平衡直方图并且删除有重复endpoint的bucket.在cust_city_id列的高度平衡直方图中51166是bucket24和bucket25的endpoint.因此bucket24将会被压缩到bucket 25中
7
5. 使用高度平衡直方图现在优化器能对谓词cust_city_id列进行更好的基数评估.例如,对于谓词cust_city_id=51806,优化器首先会检查在直方图中使用51806作为endpoint的有多少个bucket.在这种情况下136,137,138和139的endpoint是51806(查看user_histograms).优化器将会使用下面的计算公式:
(Number of bucket endpoints / total number of buckets) * number of rows in the table
在这种情况下: 4/254 * 55500=874
8
然而如果谓词cust_city_id=52500它不是任何bucket的endpoint值那么优化器会使用一个不同的计算公式.对于只是一个bucket或者不是任何bucket的endpoint的值优化器将使用下面的计算公式:
DENSITY * number of rows in the table
这里的density是对直方图使用内部算法计算出来的.density的值可以通过查看user_tab_col_statistics得到这个值是从oracle database 10.2.0.4之前使用.这个值是为了向后兼容,这个值在oracle database 9i和前期的oracle database 10g中使用.因此如果optimizer_features_enable被设置的版本比10.2.0.4前那么视图中的density值将会被使用.
9

扩展统计数据
在oracle database 11g中引入了扩展列统计数据.扩展统计数据包括另外两种额外类型的统计数:列组和表达式统计数据.

列组
在真实的数据中,在相同表中存储在不同列中的数据之间通常是有关联的.例如,在customers表中,cust_state_province列会受country_id列的影响,比如当state为California那么country只能是United States.只使用基本的列统计数据优化器是没有办法知道真实数据之间的关系如果一个语句中的where子句中有多个列来自同一个表那么有可能会计算出错误的基数.通过将这些列作为一个团体组来扩展它的统计数据使优化器知道这些真实世界的关系.

通过对一组列创建统计数据,当来自同一个表的几个列同时一起出现在语句中的where子句中时优化器能更好的评估基数.可以使用dbms_stats.create_extended_stats函数来对要收集列统计数据的这些列定义一个列组.当一个列组被创建后,当对这个表收集统计数据时和其它原始列一样oracle将自动对列组维护统计数据.
10
在创建列组和重新收集统计数据之后,在视图user_tab_col_statistics中可以看到一个系统生成名字的列.这个新列代表列组:
11

为了将系统生成列名映射到列组上可查看用户下其它扩展的统计数据,可以查询user_stat_extensions
12

现在当这些列一起出现在where子句中优化器就能使用列组统计数据而不使用单列统计数据.要注意的是并不是这个列组中的所有列都必须出现在sql语句中只要列组中的这些列的子集出现在sql语句中优化器就会使用扩展的统计数据.

表达式统计数据
也可以对一个表达式(包括函数)创建扩展的统计数据,来帮助优化器来对where子句中嵌有表达式的列进行基数评估.例如,常见的是一个where子句对一个customers表的last name使用upper函数,upper(cust_last_name)=:B1,那么对于这个表达式upper(cust_last_name)创建一个扩展的统计数据是有益的.
13
与列组一样,在表达式统计数据被定义后需要重新对这个表收集统计数据.在统计数据被收集后可能通过user_tab_col_statistics视图来查看通过系统生成的列名,它代表表达式统计数据.就象列组一样关于表达式统计数据可以在user_stat_extensions视图中可以找到.

扩展的统计数据的限制
扩展的统计数据只有当where子句中是等号谓词或者in列表时才会被使用.如果在底层的列存在直方图且列组上不存在直方图那么扩展的统计数据将不会被使用.

索引统计数据
索引统计数据提供了索引中不同值的数量(distinct keys),索引的深度(blevel),索引的叶子块数量(leaf_blocks)和集族因子.优化器使用这些信息与其它的统计数据一起来判断一个索引访问的成本.例如优化器使用blevel,leaf_blocks和表统计数据的num_rows来判断一个索引范围扫描的成本

收集统计数据
对于不断改变的数据对象统计数据也必须定期的收集才能精确的描述数据库对象.dbms_stats包是oracle推荐收集统计数据的方法用它来替代过时的analyze命令.dbms_stats包包含了超过50个不同的收集和管理统计数据的过程.但最重要的是这些过程gather_*_stats过程.这些过程被用来收集表,列和索引统计数据.运行这些过程需要需要是对象的所有者或者有analyze any的系统权限或者是dba角色.这些过程使用的参数几乎是相同的.所以这里重点介绍gather_table_stats过程.

Gather_table_stats
Dbms_stats.gather_table_stats过程可以用来收集表,分区,索引和列统计数据.虽然这个过程有15个不同的参数,但是运行这个过程时只需要指定前两个或前三个就能满足大多数用户的需求.
包含这个表的方案名
表名
如果是分区表且想要对这个特定的分区收集统计数据就是一个特定的分区名
14

其它的参数在大多数情况下可以保留其默认值.

Estimate_percent参数
这个estimate_percent参数判断在计算统计数据是使用的行记录的百分比.当表中所有的行(100%sample)被处理时收集的统计数据最精确,通常也称作计算统计数据.在oracle database 11g中引入了一种新的抽样算法是基于哈希的且提供了确定的统计数据.这种新的算法精确度与100%抽样很接近但是成本只有100%抽样的10%.当任何dbms_stats.gather_*_stats过程中的estimate_percent设置为auto_sample_size(缺省值)时就会使用这种新的算法.以前用户将estimate_percent参数设置为一个较低的值来确保统计数据能被快速的收集完成.然而没有经过详细的测试是很难知道使用什么样的抽样大小可以得到精确的统计数据.强烈建议从oracle database 11g开始让estimate_percent参数使用其缺省值(而不是显式的设置).

Method_opt参数
这个method_opt参数控制着在收集统计数据时是否创建直方图.直方图是当列中数据不是均匀分布时创建的一种特定类型的列统计数据.使用缺省值for all column size auto,oracle会基于列的使用信息(dbms_stats.report_col_usage)和列中不同值的数量来自动判断哪个列和将要使用的bucket的数量.列的使用信息反映了数据库对一个指定对象所处理的所有sql操作的一种分析.列使用跟踪缺省情况下是启用的.

如果列在where子句中以等号谓词,范围,like等形式出现那么这个列是创建直方图的一个备选者.如果在创建直方图之前列中的数据存在倾斜oracle也会验证.例如列只以等号谓词出现
且是唯一性列将不会对这个列创建直方图.

Degree参数
这个degree参数控制着用于收集统计数据的并行服务器进程的个数.通常oracle使用并行服务器进程的个数与数据字典中表的degree属性指定的值相同(并行度).缺省情况下在oracle database中所有表的这个属性被设置为1,如果对一个大表收集统计数据为了加快收集的速度这个参数是用的.当参数degree设置为auto_degree,oracle将基于对象的大小来自动判断收集统计数据时的并行服务器进程的个数.这个值的范围介于小对象的1(串行执行)到大对象的
Default_degree(parallel_threads_per_cpu * cpu_count).

Granularity参数
这个granularity参数指示了对分区表收集统计数据的级别.可能的级别是表(全局),分区或子分区.缺省情况下基于表的分区策略oracle将会判断需要使用的级别.统计数据总是在第一级分区进行收集不管分区的类型.当子分区类型是list或range时会收集子分区统计数据.如果表不是分区表这个参数会被忽略.

Cascade参数
这个cascade参数控制着是否对表中的索引收集统计数据.缺省值auto_cascade,oracle将只会对哪些统计数据过期的表重新收集索引统计数据.当一个大量数据直接加载时并且索引是禁用时节cascade通常是设置为false.在数据加载完成后,索引要被重建统计数据也会自动创建,当收集表统计数据时不需要收集索引统计数据

No_invalidate参数
这个no_invalidate参数决定在统计数据收集后游标(当收集统计数据时访问表的游标)是否立即失效.缺省值是dbms_stats.auto_invalidate,游标(已经被解析的语句)不会立即失效.它们将使用之前统计数据所创建的执行计划直到oracle基于内部启示决定依赖的游标失效为止.为了确保在共享池中没有性能问题或者如果有大量的依赖游标而且它们都是硬解析随着时间的推移失效将会发生.

改变dbms_stats.gather_*_stats中参数的缺省值
可以对一个单独的dbms_stats.gather_*_stats命令指定一个非缺省值或者对于数据库覆盖其缺省值.可以使用dbms_stats.set_*_prefs
过程来覆盖dbms_stats.gather_*_stats过程中的缺省参数值.下面是可以修改的参数列表:
AUTOSTATS_TARGET (SET_GLOBAL_PREFS only as it relates to the auto stats job) CONCURRENT (SET_GLOBAL_PREFS only)
CASCADE DEGREE ESTIMATE_PERCENT METHOD_OPT NO_INVALIDATE GRANULARITY PUBLISH INCREMENTAL STALE_PERCENT

可以使用下面的dbms_stats.set_*_prefs过程来在表,方案,数据库或全局级别来覆盖每一个参数的缺省值.
SET_TABLE_PREFS
SET_SCHEMA_PREFS
SET_DATABASE_PREFS
SET_GLOBAL_PREFS

Set_table_prefs过程允许修改用于一个特定表统计收集dbms_stats.gather_*_stats过程的缺省参数值.

Set_schema_prefs过程允许修改用于一个特定方案中所有表统计收集dbms_stats.gather_*_stats过程的缺省参数值.这个过程实际上是对一个特定方案中的每一个表调用一次set_table_prefs过程,因为它是调用set_table_prefs所以调用这个过程在其运行后不会对新创建的对象有影响.新对象将对所有参数使用golable引用值.

Set_database_prefs过程允许修改用于数据库所有用户方案统计收集dbms_stata.gather_*_stats过程的缺省参数值.这个过程实际上是对每一个用户方案中的每一个表调用set_table_prefs过程. 因为它是调用set_table_prefs所以调用这个过程在其运行后不会对新创建的对象有影响.新对象将对所有参数使用golable引用值.如果将参数add_sys设置为true那么它也有可能包含oracle所拥有的方案(sys,system等).

Set_global_prefs过程允许修改用于数据库中任何对象统计收集dbms_stats.gather_*_stats过程的缺省参数值.所有参数缺省为全局设置除非有表进行了优先设置或者这个参数通过gahter_*_stats命令被显式的设置.通过这个过程修改的参数将会影响修改之后所有新创建的对象.新对象将会使用所有参数的global_prefs.
使用set_global_prefs也可以对两个额外的参数autostat_target和concurrent.autostat_target设置缺省值来控制什么对象将会被自动统计收集job来进行统计收集.这个参数可能的值有all,oracle和auto.缺省值是auto.

Concurrent参数控制着在一个用户(或数据库)是否并发对多个表和对一个表的多个分区收集统计数据.这是一个布尔型的参数,缺省值为false.concurrent参数值不会影响自动统计收集job.

Dbms_stats.gather_*_stats过程和自动统计收集job遵循以下层次的参数值,通过命令显式设置的参数值将会对其它设置进行覆盖.如果参数没有通过命令进行设置,就会检查表级的参数引用.如果没有表级引用设置,就使用global引用.
15

如果你不能确保什么级别的参数引用被设置可以使用dbms_stats.get_prefs函数来检查.这个函数有三个参数,参数名,方案名和表名.在下面的例子中我们首先检查sh.sales表上的stale_percent的值.然后设置表级参数并且检查使用dbms_stats.get_prefs的影响.
16

自动统计收集job
通过在一个预定义的维护窗口中运行一个oracle自动任务来收丢失统计或统计过期的所有数据库对象自动收集统计数据(工作日的上午10点到零晨2点和周末的6点到零晨2点).

自动收集统计数据是通过调用内部过程
Dbms_stats.gather_database_stats_job_proc来实现的.这个过程操作非常类似如使用gahter auto选项的dbms_stats.gather_database_stats过程.主要区别是oracle内部非常重视需要统计数据的数据库对象,所以这些对象那个最需要更新统计数据谁就会被优先处理.可以通过查询dba_autotask_client_job视图或EM来验证.也可以通过EM来改变这个job的维护窗口.
17
一个表中被修改的行数超过stale_percent(缺省值10%)时就会认为这个表的统计数据过时了.oracle会监控所有表的DML活动并在SGA中进行记录.监控的信息会定时的刷新到磁盘且可以通过*_tab_modifications视图来查看.
18

也可以调用dbms_stats.flush_database_monitoring_info过程来手动刷新这些数据.如果想在查询时得到最新信息(在所有统计数据收集之前内部监控数据会被刷新).可以通过查询user_tab_statistics视图中的stale_stats列来查看哪个表的统计数据过时了.
19
表的stale_stats被设置为NO,统计数据是最新的.表的stale_stats被设置为YES,统计数据是过时的.表的stale_stats没有被设置说明丢失统计数据.

如果你有一套完善的统计数据收集过程或者因为某些原因想对主应用程序方案禁用自动统计收集,保留对数据字典表的收集.可以将dbms_stats.set_global_prefs过程的autostats_target从auto设置为oracle.

BEGIN
DBMS_STATS.SET_GLOBAL_PREFS(‘AUTOSTATS_TARGET’,’ORACLE’);
END;
/

同时一起禁用自动收集任务:
BEGIN
DBMS_AUTO_TASK_ADMIN.DISABLE(
client_name => ‘auto optimizer stats collection’,
operation => NULL,
window_name => NULL);
END;
/
提高收集统计数据的效率
当你确定了对哪些统计数据感兴趣你可能想要及时的收集这些统计数据.传统上人们希望通过使用并行来提高统计数据收集的速度.然而,如果方案中所有的对象很小并不能使用并行执行那要怎样提高统计数据收集的速度呢

并行统计数据收集
在oracle database 11g release 2(11.2.0.2)中,并行统计数据收集模式被引入来同时并发的收集一个方案中的多个表和一个表中的多个分(子分)区.对多个表和多个分(子分)区同时并行收集统计数据能充分利用多CPU的资源来减少收集的时间.

并发统计数据收集是通过全局参数来控制的,concurrent可以设置为true或false.缺省值为false.当concurrent设置为true时,oracle将会使用oracle job调度和高级队列组件来创建和管理多个并发统计数据收集job.

当concurrent设置为true时,对一个分区表调用dbms_stats.gather_table_stats使用oracle对这个分区表的每一个分区创建一个竭的统计数据收集job.这些job中有多少将会同时并发执行,这些可能job队列过程中有多少将会排队(初始化参数job_queue_processes.在rac中每一个节点的job_queue_processes)和有多不可用的系统资源.当当前运行的job完成后,更多的job会被排队和执行直到所有的分区都完成统计数据收集为止.

如果使用dbms_stats.gather_database_stats,dbms_stats.gather_schema_stats或者dbms_stats.gather_dictionary_stats收集统计数据,那么oracle将会对每一个非分区表和对分区表的每一个分区创建一个单独的统计数据收集job.每一个分区表都有一个调度job来管理它的分区job.数据库会尽可能的同时多运行几个job和余下的job将会排队直到执行的job完成.然而为了防止死锁的发生多个分区表不能同时被处理.因此如果对一个分区表运行了几个job那么其它的分区表将会进行排队等待直到当前的收集job完成为止.对于非分区表没有限制.

下面的数据说明了在sh方案中使用dbms_stats.gather_schema_stats命令在不同级别创建job.
Oracle将会对每一个非分区表创建一个统计数据收集job.
CHANNELS,
COUNTRIES,
CUSTOMERS,
PRODUCTS,
PROMOTIONS,
TIMES
每一个分区表一个调度job.比如,sales和costs.且它将对sales和costs中的每一个分区创建一个统计数据收集job.
20

假设job_queue_processes参数设置为32,oracle job调度器允许运行32个统计数据收集job.假设costs表的第29个job开始执行,那么三个非分区表统计数据收集job也会开始运行,sales的统计数据收集job将会自动排队,因为一次只能处理一个分区表.当每一个job完成后,另外的job将会进行排队和开始执行直到所有64个job全部完成为止.每一个单独的统计数据收集job都能使用并行执行.

配置和设置
在oracle database 11.2.0.2中,统计数据收集的并行设置缺省情况下是关闭的.可以使用下面的命令将其打开.
BEGIN
DBMS_STATS.SET_GLOBAL_PREFS(‘CONCURRENT’,’TRUE’);
END;
/
为了收集统计数据可能也会需要一些额外的权限.用户必须要有job scheduler和AQ权限:
CREATE JOB
MANAGE SCHEDULER
MANAGE ANY QUEUE

当job scheduler在sysaux表空间中存储它的内部表和视图时sysaux表空间应该是联机状态.最后对于统计数据收集过程来说job_queue_processes参数的设置应该要能完全利用可用的系统资源.如果不计划使用并行执行应该将job_queue_processes设置为cpu核数的2倍(在rac中需要对每一个节点进行设置).要确保在系统级别设置这个参数(alter system..或者通过init.ora文件)而不是在会话级别进行设置(alter session).

如果将使用并行执行作为并发统计数据收集的一部分应该禁用parallel_adaptive_multi_user初始化参数.
ALTER SYSTEM SET parallel_adaptive_multi_user=false;
也建议启用并行语句队列.这要求资源管理器是激活的且创建一个临时资源计划组资源消耗组”OTHER_GROUPS”将启用排队.通常资源管理器只在维护窗口期间是激活的.下面的脚本将创建一个临时资源计划并对这个计划使用资源管理器.
21
你会注意到自动统计数据收集job现在不会使用并发.将concurrent设置为true对自动统计数据收集job不会有影响.

分区表的统计数据收集
分区表的统计数据收集它是由表级别和分区级别的统计数据收集组成.在oracle database 11g之前,新增加一个分区或修改一个分区都要求扫描整个表来重新刷新表级别的统计数据.如果跳过全局层面的统计数据收集那么优化器会基于已经存在的分区级别的统计数据来推断全局层面的统计数据.这种方法对于简单表的统计数据来说是精确的比如象行数可以通过聚合每一个分区的行数来统计—但是其它的统计数据是不能精确的判断的.比如,不能基于所有分区的单个分区来精确的判断一个列中的不同值的数量(优化器使用的最关键的统计数据).

在oracle database 11g中通过引入增量全局统计数据增加了对分区表的统计数据收集.如果一个分区的incremental参数设置为true,dbms_stats.gather_*_stats参数granularity为global,estimate_percent设置为auto_sample_size. Oracle将会对新的分区收集统计数据并通过扫描新增加的或修改的分区来更新所有全局层面的统计数据但不是扫描整个表.

增量全局统计数据是通过为表中每一个分区存储的概要来计算出来的.一个概要是分区和分区中列的统计数据的元数据.每一个概要存储在sysaux表空间中.全局统计数据是通过聚合分区级别的统计数据和每一个分区的概要生成的,因此消除了为了收集表级别统计数据而要扫描整个表.当一个新的分区被增加到表中,仅需要对新增加的分区收集统计数据.全局统计数据会自动使用新的分区概要和已经存在的分区概要来精确的更新.
22

下面是使用增量全局统计数据的步骤
在表级或全局层面启用增量统计数据
BEGIN
DBMS_STATS.SET_TABLE_PREFS(‘SH’,’SALES’,’INCREMENTAL’,’TRUE’);
END;
/
为了检查一个指定表的incremental的当前设置可以使用dbms_stats.get_prefs;
SELECT DBMS_STATS.GET_PREFS(‘INCREMENTAL’,’SH’,’SALES’) FROM dual;
注意incremental不会应用到子分区上.子分区和分区的统计数据会正常的被收集.只是分区统计数据将会用来确定全局或表级别的统计数据.

管理统计数据
为了收到适当的统计数据,提供一种全面框架来管理它们也很重要.oracle提供了一系列的方法来做这件事包括将统计数据还原到之前的版本,将统计数据从一个系统传输到另一个系统或者甚至手动设置统计数据.这些选项在特定情况下是非常有用的,但是不建议替换标准的收集统计数据的方法dbms_stats.

还原统计数据
从oracle database 10g开始,当你使用dbms_stats收集统计数据时原始的统计数据会自动在数据字典表中进行备份, 如果新收集的统计数据导致任何的问题通过运行dbms_stats.restore_table_stats就能很容易的还原.视图dba_tab_stats_history包含了每一个表统计数据保存的时间戳.

下面的例子将sales表的统计数据还原成昨天收集的且在shared_pool中引用sales表的所有游标会自动变为无效.想让所有的游标变为无效因为将统计数据还原成昨天的想让它们立即影响所有游标.no_invalidate参数值会判断引用该表的游标是否会变为无效.
BEGIN
DBMS_STATS.RESTORE_TABLE_STATS(ownname => ‘SH’,
tabname => ‘SALES’,
as_of_timestamp => SYSTIMESTAMP-1
force => FALSE,
no_invalidate => FALSE);
END;
/

Pending统计数据
通常情况下当收集统计数据时,收集到的统计数据会被立即发布(写)到适当的数据字典表并提供给优化器使用.在oracle database 11g中可以将收集到的统计数据不立即发布,而是将它们存储为一个没有发布的pending状态.它们不是存储在常用的数据字典表中而是存储在pending表所以在发布它们之前可以对它们进行测试.这些pending统计数据可以以一种受控的方式对单个会话启用,这样就可以在发布之前对这些统计数据进行验证.为了启用pending统计数据收集需要使用dbms_stats.set_*_prefs这样的一个过程对你想创建pending统计数据的对象将参数publish从true改变false.
BEGIN
DBMS_STATS.SET_TABLE_PREFS(‘SH’,’SALES’,‘PUBLISH’,’FALSE’);
END;
/

收集统计数据
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(‘SH’,’SALES’);
END;
/
对这些对象收集的统计数据可以通过user_*_pending_stats来显示.可以通过一个alter session命令来设置初始化参数optimizer_use_pending_stats为true来告诉优化器使用pending统计数据并运一个sql,对于访问的表没有pending统计数据优化器将会使用标准数据字典表中的当前统计数据.当对pending统计数据进行验证后可以使用下面的过程将其发布.
DBMS_STATS.PUBLISH_PENDING_STATS.
BEGIN
DBMS_STATS.PUBLISH_PENDING_STATS(‘SH’,’SALES’);
END;
/

导出/导入统计数据
当开发一个新应用程序或一个新模块想要测试时,理想情况下想要测试系统与生产系统的硬件平台和数据大小是相同的.这种情况并不总是可以满足的.最常见的问题是生产系统的大小.通过将一个产生数据库中的优化统计数据复制到另一个相同数据库版本的系统中,这样就可以模仿生产环境中的行为.生产数据库中的统计数据可以使用dbms_stats.export_*_stats和dbms_stats.import_*_stats过程复制到测试系统中.

在导出统计数据之前需要使用dbms_stats.create_stat_table过程来创建一个表来存储这些统计数据.在这个表创建之后可以使用dbms_stats.export_*_stats过程从数据字典中导出统计数据.当统计数据填充到统计数据表中,可以使用datadump从生产数据库中将统计数据表中的数据导出并导入到测试数据库中.当统计数据表完全导入到测试数据库中,可以使用dbms_stats.import_*_stats过程导入到数据字典表.下面的例子将创建一个统计数据表MYSTATS并从sh方案中将统计数据导出到MYSTATS统计数据表中.
23

复制分区统计数据
当处理分区表时优化器依赖整个表(全局统计数据)统计数据和单个分区统计数据来给一个sql语句选择一个好的执行计划.如果查询需要访问的只有单个分区优化器只会使用要访问的这个分区的统计数据.如果查询要访问多个分区优化器将会使用全局统计数据和分区统计数据.

常见的一个范围分区表有一个新的分区增加到一个存在的表中并向这个增加的分区插入数据.如果终端用户在统计数据收集之前要查询新插入的数据,由于过时的统计数据可能会选择一个次优的执行计划.最常见的一种情况是where子句中谓词使用值超出了列统计数据中[最小值,最大值]的范围.这就是一个’out-of-range’的错误.
超出范围的条件可以使用dbms_stats.copy_table_stats过程(从oracle database 10.2.0.4开始可以使用)来预防.这个过程复制一个代表源(子)分区的统计数据到一个新创建的空的(子)分区中.它也会复制依赖对象的统计数据:列,本地(分区)索引等等.按下面的方式来调整分区列的最小值和最大值.
如果分区类型是hash分区那么目标分区的最小值和最大值与源分区相同.

如果分区类型是list分区且目标分区是一个not default分区,那么目标分区的最小值将被设置为描述目标分区列表值中的最小值.最大值将被设置为描述目标分区列表值中的最大值.

如果分区炻是list分区且目标分区是一个default分区,那么目标分区的最小值被设置为源分区中的最小值,目标分区的最大值被设置为源分区中的最大值.

如果分区类型是range那么目标分区的最小值设置为之前分区的上限值,目标分区的最大值设置为目标分区的上限值除非目标分区的上限值是maxvalue,在这种情况下目标分区的最大值设置为之前分区的上限值.

它也能基于指定的比例来复制统计数据(比如块数或行数).下面的命令将sales_q3_2011范围分区的统计数据复制到sales表的sales_q4_2011分区中.比例因子为2.
BEGIN
DBMS_STATS.COPY_TABLE_STATS(‘SH’,’SALES’,’SALES_Q3_2002′,’SALES_Q4_2002′, 2);
END;
/

如果索引分区名与表分区一样只会复制索引统计数据.全局或表级统计数据缺省情况下不会被更新.全局层面的统计数据只有调用dbms_stats.copy_table_stats时没有全局统计数据存在时通过聚合生成全局统计数据时才会被影响.

比较统计数据
一个系统中sql语句的执行计划与另一个系统中的执行计划不同一个关键的原因变是每个系统中的统计数据不同,例如测试系统中的数据不是100%的与生成系统的中相同.为了识别不同的统计数据
Dbms_stats.diff_table_stats_*函数可以用来比较两个不同数据来源的表统计数据.统计数据来源可以是:
一个用户统计数据表和数据字典中的当前统计数
单个用户统计数据表包含两组统计数据可以通过statids来识别
两个不同用户统计数据表
历史中的两个时间点的统计数据
当前统计数与厍上的一个时间点的统计数据
Pending统计数据与数据字典中当前的统计数据
Pending统计数据与用户统计数据表
这个函数也可以比较两个单独对象的统计数据(索引,列,分区)而且如果统计数据之间的差别超过了指定的阈值也俑显示两个来源对象的所有统计数据.这个阈值可以通过一个函数的参数来指定缺省值是10%.第一个来源的统计数据将会用作计算不同百分比的基数.

下面的例子中将用当前数据字典中emp表的统计数据与统计数据表TAB1中emp表的统计数据进行比较,下面的sql语句将会生成了一个报告:
SELECT report, maxdiffpct
FROM table(DBMS_STATS.DIFF_TABLE_STATS_IN_STATTAB(‘SCOTT’,’EMP’,’TAB1’ ));

24
锁定统计数据
在有些情况下,可以通过锁定统计数据来阻止对表或方案收集新的统计数据.当统计数据被锁定后这些统计数据直到这些统计数据被解锁或者使用gather_*_stats过程时将参数force设置为true否则是不会被修改的.
25
在oracle database 11g中dbms_stats包被扩展到允许统计数据在分区级别被锁定和解锁.这些额外的过程允许更细粒度的来控制.
BEGIN
DBMS_STATS.LOCK_PARTITION_STATS(‘SH’,’SALES’, ‘SALES_Q3_2000’);
END;
注意这里有一种层次结构的锁定统计数据.例如,如果对一个分区表锁定统计数据,然后为了对一个分区重新收集统计数据需要对这一个分区解锁统计数据这时就会收到ora-20005错误.出现这个错误是因为分区虽然已经解锁但表级仍然被锁定.这时要对分区重新收集统计数据只在将force参数设置为true才能成功.
26

手动设置统计数据
在某些特定的情况下手动设置存储在数据字典中的优化器统计数据是非常有效的.比如一个高度不稳定的全局临时表(注意虽然这里里讨论手动设置统计数据,但是不建议这么做.因为不精确或不一致的统计数据可能导致选择性能极差的执行计划).统计数据可能调用
Dbms_stats.set_*_stats过程来手动设置.

其它类型的统计数据
除了基本表,列和索引统计数据之外优化器还使用额外的信息来判断一个语句的执行计划.额外的信息包含动态抽样和系统统计数据.

动态抽样
动态抽样是在oracle database 9i release 2中引入用来在优化一个sql语句时收集额外的语句特定对象的统计数据.最常见的误解是动态抽样可以用来替优化器统计数据.动态抽样的目的是当常规的统计数据不能满足良好的基数评估时来扩大现有的统计数据.

所以怎样和何时使用动态抽样呢.在编译一个sql语句时优化器通过考虑可用的统计数据是否能生成一个良好的执行计划来决定是否使用动态抽样.如果可用的统计数据不满足就会使用动态抽样.它通常是用来弥补由于丢失或无效的统计数据导致一个糟糕的执行计划的情况.对于一个查询中的一个或多个表没有统计数据,优化器在优化语句之前将会使用动态抽样来对这些表生成基本的统计数据.动态抽样收集的统计数据质量不高或者没有使用dbms_stats收集的完整.

第二种使用动态抽样的情况是当语句包含一个复杂的谓词表达式且扩展统计数据不可以使用或者不能使用的时候.例如,一个查询在两个相关列之间使用的不是等号谓词.在这种情况下标准的统计数据不能满足判断,优化器假设每一个谓词将会减少查询所要返回的行数.根据标准的统计数据判断的基数是20197但实际上返回的行数是210420.
SELECT count(*)
FROM sh.Sales
WHERE cust_id < 2222 AND prod_id > 5;
27

使用标准的统计数据优化器不能识别sales表中cust_id与prod_id之间的关联.通过设置
Optimizer_dynamic_sampling为级别6,优化器将使用动态抽样对复杂谓词表达式收集额外的信息.通过动态抽样提供的额外信息允许优化器生成一个更精确的基数评估因此会选择一个更好的执行计划.
28
正如你所看到的一样动态抽样是由参数optimizer_dynamic_sampling来控制的,它可能设置为不同的级别(0-10).这些级别控制着两件不同的事;当动态抽样开始时使用多大的抽样大小来生成统计数据.太大的抽样大小,动态抽样对编译查询的时间的影响就会越大.

从oracle database 11g release 2开始,优化器将会自动决定动态抽样是否有用和对于并行执行的sql语句使用什么样的动态抽样级别.这个决定是基于语句中表的大小和谓词的复杂程度.然而如果optimizer_dynamic_sampling参数被显式的设置为一个非缺省值,那么用户指定的值将会被使用.可以查看执行计划的note部分来了解是否使用了动态抽样.例如,如果对sales表启用了并行执行,执行下面的查询,优化器将会自动使用级别为4的动态抽样
29

对于串行执行的sql语句,动态抽样的级别将根据optimizer_dynamic_sampling参数来决定.将不是由优化器自动触发.原因是串行语句通常运行时间短且在编译时的任何开销都可能影响它的性能.而并行语句会使用更多的资源,所以在编译时产生的额外开销对获得更好的执行计划来说是值得的.

系统统计数据
在oracle database 9i中,系统统计数据引入是为了让优化器通过使用关于执行语句的真实系统硬件信息比如cpu速度和IO性能来更精确的计算出执行计划中每一步的成本.

系统统计数据缺省情况下是启用且缺省值是自动初始化的,这些值代表了大多数系统.当收集系统统计数据时它们将会覆盖这些初始值.为了收集系统统计数据可以在一个有代表性的工作负载时间窗口使用dbms_stats.gather_system_stats,理想的情况是在负载高峰期间收集.

系统统计数据只需收集一次.系统统计数据不是作为自动统计数据收集job的一部分也不会自动被收集.必须使用gahter_system_statistics来更新系统统计数据.

字典表统计数据
因为现在优化器只支持基于成本的优化器,数据库中所有的表都需要有统计数据包括所有字典表(sys,system用户所拥有的表它们存储在system和sysaux表空间中).字典表的统计数据是由在晚上维护窗口运行的自动统计数据收集job来维护的.如果想对应用程序用户关闭自动统计数据收集job任务但是对字典表保留.可以调用dbms_stats.set_global_prefs将autostats_target参数从auto改为oracle.

BEGIN
DBMS_STATS.SET_GLOBAL_PREFS(‘AUTOSTATS_TARGET’,’ORACLE’);
END;
/
字典表的统计数据通过使用dbms_stats.gather_dictionary_stats过程来手动设置.用户必须要有analyze any dictionary和analyze any系统权限或者dba角色来更新字典表统计数据.建议对字典表的统计数据维护与常规用户方案统计数据维护方式保持一致.

固定表的统计数据
需要对动态性能表和它们的索引(固定对象)收集统计数据.它们有x$表和v$视图.因为v$视图可能会象其它的用户表或视图一样出现在sql语句中.对这些表收集统计数据来让优化器生成一个更好的执行计划来说是很重要的.然而不象其它的数据库表,当优化统计数据丢失时不会对语句调用的x$表使用动态抽样.优化器会使用预先定义的缺省统计数据.这些缺省值可能没有代表性且可能会导致选择一个次优的执行计划.在系统中可能会造成一些性能问题.因为这个原因强烈建议收集固定对象的统计数据.

自动统计数据收集job不能对固定对象的统计数据进行收集或维护.可以使用dbms_stats.gather_fixed_objects_stats过程来收集固定对象的统计数据
BEGIN
DBMS_STATS.GATHER_FIXED_OBJECTS_STATS;
END;
/
这个dbms_stats.gather_fixed_objects_stats过程除了数据块的数量之外与dbms_stats.gather_table_stats过程收集的统计数据是一样的.因为x$表只是在内存结构中不存储到磁盘所在块总是为0.因为x$表的瞬态性很重要当系统中有一个有代表性的工作负载就是对固定对象收集统计数据的时候.用户必须要有analyze any dictionary系统权限或者dba角色来更新固定对象统计数据.当你对数据库或程序进行升级后强烈建议重新收集固定对象的统计数据.

小结
为了让基于成本的优化器精确的判断一个执行计划的成本,它必须有sql语句所访问的所有对象的信息和关于运行这个语句的系统方面的信息.这些必要的信息通常被称作优化器统计数据.理解和管理优化器统计数据是优化sql执行的关键.知道何时以及怎样及时的收集统计数据是维护良好性能的关键.

通过使用自动统计数据收集job和dbms_stats包.dba可以对系统维护一组精确的统计数据来确保优化器将有最好的资源信息来确定语句的执行计划.

使用SQL Profile进行SQL优化案例

一个社保系统的自助查询系统查询个人医疗费用明细的查询语句要用一分多钟还没查询出来,语句如下:

select * from  v_zzzd_ylbx_ylfymxcx where aac002='430703198202280017'

从上面的语句可知是从视图 v_zzzd_ylbx_ylfymxcx中查询数据。v_zzzd_ylbx_ylfymxcx视图的创建语句如下:

create or replace view v_zzzd_ylbx_ylfymxcx as
select a.indi_id aac001,a.idcard aac002,'' aof008,a.center_id aab301,
       a.name aac003,a.hospital_id akf008,d.hospital_name akf009,a.serial_no akf010,
       f.biz_name akf011,
       nvl(round(sum(b.real_pay),2),0) akf012,
       nvl(round(sum(case when b.fund_id = '003' then b.real_pay else 0 end),2),0) akf013, 0 akf014,0 akf015,0 akf016,
       nvl(round(sum(case when b.fund_id = '001' then b.real_pay else 0 end),2),0) ak093,
       nvl(round(sum(b.real_pay),2),0) - nvl(round(sum(case when b.fund_id in ('999','003') and b.policy_item_code in ( 'S00','S01','C001','C004''C006') then b.real_pay else 0 end),2),0) ak092,
       nvl(round(sum(case when b.fund_id in ('999','003') and b.policy_item_code in ( 'S00','S01','C001','C004''C006') then b.real_pay else 0 end),2),0) ak094,
       nvl(round(sum(case when b.fund_id in('003', '999') then b.real_pay else 0 end),2),0) ak095,
       a.fin_date akf017,to_char(nvl(a.in_days,0)) akf018,
       nvl(round(sum(case when b.fund_id = '003' then b.real_pay else 0 end),2),0) akf019,
       nvl(round(sum(case when b.fund_id in( '001','201','301' ) then b.real_pay else 0 end),2),0) akf020
from  bs_insured h,mt_biz_fin a ,mt_pay_record_fin b,bs_disease c,bs_hospital d ,bs_hosp_level e ,bs_biztype f,bs_corp g
where h.indi_id=a.indi_id
  and a.hospital_id = b.hospital_id
  and a.serial_no = b.serial_no
  and a.biz_type = f.biz_type
  and a.center_id = f.center_id
  and a.center_id=c.center_id
  and a.fin_disease=c.icd
  and a.hospital_id = d.hospital_id
  and d.hosp_level=e.hosp_level
  and a.biz_type in ('10','11','12','13','16','17')
  and a.valid_flag = 1
  and b.valid_flag = 1
  and a.pers_type in ('1','2')
  and a.corp_id = g.corp_id
group by a.indi_id ,a.idcard ,a.center_id,a.name ,a.hospital_id ,d.hospital_name,a.serial_no , f.biz_name,a.fin_date,a.in_days;

生成SQL Profile有两种方式:自动和手动方式,这里使用自动方式来生成SQL Profile.
下面创建一个SQL自动调整优化任务:

SQL> declare 
  2   my_task_name varchar2(30);
  3   my_sqltext clob;
  4  begin
  5   my_sqltext :='select * from  v_zzzd_ylbx_ylfymxcx where aac002=''430703198202280017''';
  6   my_task_name :=dbms_sqltune.create_tuning_task(
  7           sql_text => my_sqltext,
  8           user_name => 'INSUR_CHANGDE',
  9           scope=>'COMPREHENSIVE',
 10          time_limit=>60,
 11          task_name => 'my_sql_tuning_task_2014080803',
 12          description=>'Task to tune a query on table v_zzzd_ylbx_ylfymxcx');
 13  end;
 14  /

PL/SQL procedure successfully completed.

SQL> 
SQL> begin
  2  dbms_sqltune.execute_tuning_task(task_name=>'my_sql_tuning_task_2014080803');
  3  end;
  4  /

PL/SQL procedure successfully completed.


通过下面的语句查询优化建议

SQL>select dbms_sqltune.report_tuning_task('my_sql_tuning_task_2014080803') from dual;


GENERAL INFORMATION SECTION
-------------------------------------------------------------------------------
Tuning Task Name                  : my_sql_tuning_task_2014080803
Tuning Task Owner                 : INSUR_CHANGDE
Scope                             : COMPREHENSIVE
Time Limit(seconds)               : 60
Completion Status                 : COMPLETED
Started at                        : 08/08/2014 19:42:47
Completed at                      : 08/08/2014 19:43:49
Number of Index Findings          : 1
Number of SQL Restructure Findings: 1
Number of Errors                  : 1

-------------------------------------------------------------------------------
Schema Name: INSUR_CHANGDE
SQL ID     : 0rpt6bzp60cjm
SQL Text   : select * from  v_zzzd_ylbx_ylfymxcx where
             aac002='430703198202280017'

-------------------------------------------------------------------------------
FINDINGS SECTION (2 findings)
-------------------------------------------------------------------------------

1- Index Finding (see explain plans section below)
--------------------------------------------------
  通过创建一个或多个索引可以改进此语句的执行计划。

  Recommendation (estimated benefit: 99.98%)
  ------------------------------------------
  - 考虑运行可以改进物理方案设计的 Access Advisor 或者创建推荐的索引。
    create index INSUR_CHANGDE.IDX$$_429C0001 on
    INSUR_CHANGDE.MT_BIZ_FIN("IDCARD",TO_NUMBER("VALID_FLAG"),"PERS_TYPE","BIZ_
    TYPE");
    这里在创建IDX$$_429C0001索引时,TO_NUMBER("VALID_FLAG")这是因为表MT_BIZ_FIN中的valid_flag是varchar2而视图定义中写成了valid_flag=1的原因
     
  - 考虑运行可以改进物理方案设计的 Access Advisor 或者创建推荐的索引。
    create index INSUR_CHANGDE.IDX$$_429C0002 on
    INSUR_CHANGDE.MT_PAY_RECORD_FIN("HOSPITAL_ID","SERIAL_NO");

  Rationale
  ---------
    创建推荐的索引可以显著地改进此语句的执行计划。但是, 使用典型的 SQL 工作量运行 "Access Advisor"
    可能比单个语句更可取。通过这种方法可以获得全面的索引建议案, 包括计算索引维护的开销和附加的空间消耗。

2- Restructure SQL finding (see plan 1 in explain plans section)
----------------------------------------------------------------
  谓词 TO_NUMBER("A"."VALID_FLAG")=1 (在执行计划的行 ID 9 处使用) 包含索引列 "VALID_FLAG"
  的隐式数据类型转换。此隐式数据类型转换使优化程序无法有效地使用表 "INSUR_CHANGDE"."MT_BIZ_FIN" 的索引。
  这是因为表MT_BIZ_FIN中的valid_flag是varchar2而视图定义中写成了valid_flag=1的原因
  Recommendation
  --------------
  - 将谓词重写为等价型以便利用索引。

  Rationale
  ---------
    如果谓词是不等式条件或者如果存在关于索引列的表达式或隐式数据类型转换, 则优化程序无法使用索引。

-------------------------------------------------------------------------------
ERRORS SECTION
-------------------------------------------------------------------------------
- 当前操作因超时而中断。这是因为优化任务设置的超时时间为60秒的原因
-------------------------------------------------------------------------------
EXPLAIN PLANS SECTION
-------------------------------------------------------------------------------

1- Original
-----------
Plan hash value: 3562745886

---------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name                    | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                         |     7 |  1505 |   127K  (2)| 00:25:25 |
|   1 |  HASH GROUP BY                      |                         |     7 |  1505 |   127K  (2)| 00:25:25 |
|   2 |   NESTED LOOPS                      |                         |     7 |  1505 |   127K  (2)| 00:25:25 |
|   3 |    NESTED LOOPS                     |                         |     7 |  1491 |   127K  (2)| 00:25:25 |
|   4 |     NESTED LOOPS                    |                         |     7 |  1253 |   127K  (2)| 00:25:25 |
|   5 |      NESTED LOOPS                   |                         |     7 |  1127 |   127K  (2)| 00:25:25 |
|   6 |       NESTED LOOPS                  |                         |     7 |  1085 |   127K  (2)| 00:25:25 |
|   7 |        NESTED LOOPS                 |                         |    14 |  1554 |   127K  (2)| 00:25:25 |
|   8 |         NESTED LOOPS                |                         |    14 |  1484 |   127K  (2)| 00:25:25 |
|*  9 |          TABLE ACCESS FULL          | MT_BIZ_FIN              |    14 |  1232 |   127K  (2)| 00:25:25 |
|  10 |          TABLE ACCESS BY INDEX ROWID| BS_BIZTYPE              |     1 |    18 |     1   (0)| 00:00:01 |
|* 11 |           INDEX UNIQUE SCAN         | PK_BS_BIZTYPE           |     1 |       |     1   (0)| 00:00:01 |
|* 12 |         INDEX UNIQUE SCAN           | PK_BS_CORP              |     1 |     5 |     1   (0)| 00:00:01 |
|* 13 |        TABLE ACCESS BY INDEX ROWID  | MT_PAY_RECORD_FIN       |     1 |    44 |     1   (0)| 00:00:01 |
|* 14 |         INDEX RANGE SCAN            | IDX_MT_PAY_RECORD_FIN_1 |     1 |       |     1   (0)| 00:00:01 |
|* 15 |       INDEX UNIQUE SCAN             | PK_BS_INSURED           |     1 |     6 |     1   (0)| 00:00:01 |
|* 16 |      INDEX RANGE SCAN               | INX_BS_DISEASE_01       |     1 |    18 |     1   (0)| 00:00:01 |
|  17 |     TABLE ACCESS BY INDEX ROWID     | BS_HOSPITAL             |     1 |    34 |     1   (0)| 00:00:01 |
|* 18 |      INDEX UNIQUE SCAN              | PK_BS_HOSPITAL          |     1 |       |     1   (0)| 00:00:01 |
|* 19 |    INDEX UNIQUE SCAN                | PK_BS_HOSP_LEVEL        |     1 |     2 |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------

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

   9 - filter("A"."IDCARD"='430703198202280017' AND TO_NUMBER("A"."VALID_FLAG")=1 AND
              ("A"."PERS_TYPE"='1' OR "A"."PERS_TYPE"='2') AND ("A"."BIZ_TYPE"='10' OR "A"."BIZ_TYPE"='11' OR
              "A"."BIZ_TYPE"='12' OR "A"."BIZ_TYPE"='13' OR "A"."BIZ_TYPE"='16' OR "A"."BIZ_TYPE"='17'))
  11 - access("A"."CENTER_ID"="F"."CENTER_ID" AND "A"."BIZ_TYPE"="F"."BIZ_TYPE")
  12 - access("A"."CORP_ID"="G"."CORP_ID")
  13 - filter(TO_NUMBER("B"."VALID_FLAG")=1)
  14 - access("A"."HOSPITAL_ID"="B"."HOSPITAL_ID" AND "A"."SERIAL_NO"="B"."SERIAL_NO")
  15 - access("H"."INDI_ID"="A"."INDI_ID")
  16 - access("A"."CENTER_ID"="C"."CENTER_ID" AND "A"."FIN_DISEASE"="C"."ICD")
  18 - access("A"."HOSPITAL_ID"="D"."HOSPITAL_ID")
  19 - access("D"."HOSP_LEVEL"="E"."HOSP_LEVEL")

这是按优化建议创建两个索引后的执行计划

2- Using New Indices  
--------------------
Plan hash value: 2373509962

----------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                   |     7 |  1505 |    14   (8)| 00:00:01 |
|   1 |  HASH GROUP BY                       |                   |     7 |  1505 |    14   (8)| 00:00:01 |
|   2 |   NESTED LOOPS                       |                   |     7 |  1505 |    13   (0)| 00:00:01 |
|   3 |    NESTED LOOPS                      |                   |     7 |  1470 |    12   (0)| 00:00:01 |
|   4 |     NESTED LOOPS                     |                   |     7 |  1428 |    11   (0)| 00:00:01 |
|   5 |      NESTED LOOPS                    |                   |     7 |  1302 |    10   (0)| 00:00:01 |
|   6 |       NESTED LOOPS                   |                   |     7 |  1288 |     9   (0)| 00:00:01 |
|   7 |        NESTED LOOPS                  |                   |     7 |  1050 |     7   (0)| 00:00:01 |
|   8 |         NESTED LOOPS                 |                   |    14 |  1484 |     4   (0)| 00:00:01 |
|   9 |          INLIST ITERATOR             |                   |       |       |            |          |
|  10 |           TABLE ACCESS BY INDEX ROWID| MT_BIZ_FIN        |    14 |  1232 |     2   (0)| 00:00:01 |
|* 11 |            INDEX RANGE SCAN          | IDX$$_429C0001    |    14 |       |     1   (0)| 00:00:01 |
|  12 |          TABLE ACCESS BY INDEX ROWID | BS_BIZTYPE        |     1 |    18 |     1   (0)| 00:00:01 |
|* 13 |           INDEX UNIQUE SCAN          | PK_BS_BIZTYPE     |     1 |       |     1   (0)| 00:00:01 |
|* 14 |         TABLE ACCESS BY INDEX ROWID  | MT_PAY_RECORD_FIN |     1 |    44 |     1   (0)| 00:00:01 |
|* 15 |          INDEX RANGE SCAN            | IDX$$_429C0002    |     1 |       |     1   (0)| 00:00:01 |
|  16 |        TABLE ACCESS BY INDEX ROWID   | BS_HOSPITAL       |     1 |    34 |     1   (0)| 00:00:01 |
|* 17 |         INDEX UNIQUE SCAN            | PK_BS_HOSPITAL    |     1 |       |     1   (0)| 00:00:01 |
|* 18 |       INDEX UNIQUE SCAN              | PK_BS_HOSP_LEVEL  |     1 |     2 |     1   (0)| 00:00:01 |
|* 19 |      INDEX RANGE SCAN                | INX_BS_DISEASE_01 |     1 |    18 |     1   (0)| 00:00:01 |
|* 20 |     INDEX UNIQUE SCAN                | PK_BS_INSURED     |     1 |     6 |     1   (0)| 00:00:01 |
|* 21 |    INDEX UNIQUE SCAN                 | PK_BS_CORP        |     1 |     5 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

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

  11 - access("A"."IDCARD"='430703198202280017' AND "MT_BIZ_FIN".???)
       filter("A"."BIZ_TYPE"='10' OR "A"."BIZ_TYPE"='11' OR "A"."BIZ_TYPE"='12' OR
              "A"."BIZ_TYPE"='13' OR "A"."BIZ_TYPE"='16' OR "A"."BIZ_TYPE"='17')
  13 - access("A"."CENTER_ID"="F"."CENTER_ID" AND "A"."BIZ_TYPE"="F"."BIZ_TYPE")
  14 - filter(TO_NUMBER("B"."VALID_FLAG")=1)
  15 - access("A"."HOSPITAL_ID"="B"."HOSPITAL_ID" AND "A"."SERIAL_NO"="B"."SERIAL_NO")
  17 - access("A"."HOSPITAL_ID"="D"."HOSPITAL_ID")
  18 - access("D"."HOSP_LEVEL"="E"."HOSP_LEVEL")
  19 - access("A"."CENTER_ID"="C"."CENTER_ID" AND "A"."FIN_DISEASE"="C"."ICD")
  20 - access("H"."INDI_ID"="A"."INDI_ID")
  21 - access("A"."CORP_ID"="G"."CORP_ID")

-------------------------------------------------------------------------------

因为前一次优化任务因为超时中断了所以再次进行SQL自动优化任务,并将超时时间设置为600秒

SQL> declare 
  2   my_task_name varchar2(30);
  3   my_sqltext clob;
  4  begin
  5   my_sqltext :='select * from  v_zzzd_ylbx_ylfymxcx where aac002=''430703198202280017''';
  6   my_task_name :=dbms_sqltune.create_tuning_task(
  7           sql_text => my_sqltext,
  8           user_name => 'INSUR_CHANGDE',
  9           scope=>'COMPREHENSIVE',
 10          time_limit=>600,
 11          task_name => 'my_sql_tuning_task_2014080804',
 12          description=>'Task to tune a query on table v_zzzd_ylbx_ylfymxcx');
 13  end;
 14  /

PL/SQL procedure successfully completed.

SQL> 
SQL> begin
  2  dbms_sqltune.execute_tuning_task(task_name=>'my_sql_tuning_task_2014080804');
  3  end;
  4  /

PL/SQL procedure successfully completed.

通过下面的语句查询优化建议

SQL>select dbms_sqltune.report_tuning_task('my_sql_tuning_task_2014080806') from dual;

GENERAL INFORMATION SECTION
-------------------------------------------------------------------------------
Tuning Task Name                  : my_sql_tuning_task_2014080804
Tuning Task Owner                 : INSUR_CHANGDE
Scope                             : COMPREHENSIVE
Time Limit(seconds)               : 600
Completion Status                 : COMPLETED
Started at                        : 08/08/2014 20:03:46
Completed at                      : 08/08/2014 20:04:27
Number of SQL Profile Findings    : 1

-------------------------------------------------------------------------------
Schema Name: INSUR_CHANGDE
SQL ID     : 0rpt6bzp60cjm
SQL Text   : select * from  v_zzzd_ylbx_ylfymxcx where
             aac002='430703198202280017'

-------------------------------------------------------------------------------
FINDINGS SECTION (1 finding)
-------------------------------------------------------------------------------

1- SQL Profile Finding (see explain plans section below)
--------------------------------------------------------
  为此语句找到了性能更好的执行计划。

  Recommendation (estimated benefit: 28.75%)
  ------------------------------------------
  - 考虑接受推荐的 SQL 概要文件。
    execute dbms_sqltune.accept_sql_profile(task_name =>
            'my_sql_tuning_task_2014080804', replace => TRUE);

-------------------------------------------------------------------------------
EXPLAIN PLANS SECTION
-------------------------------------------------------------------------------

1- Original With Adjusted Cost
------------------------------
Plan hash value: 3514293130

-----------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                   |   251 | 53965 |    36   (6)| 00:00:01 |
|   1 |  HASH GROUP BY                        |                   |   251 | 53965 |    36   (6)| 00:00:01 |
|   2 |   NESTED LOOPS                        |                   |   251 | 53965 |    35   (3)| 00:00:01 |
|   3 |    NESTED LOOPS                       |                   |   252 | 52920 |    34   (3)| 00:00:01 |
|   4 |     NESTED LOOPS                      |                   |   252 | 51408 |    33   (4)| 00:00:01 |
|*  5 |      HASH JOIN                        |                   |   251 | 46686 |    32   (4)| 00:00:01 |
|*  6 |       TABLE ACCESS BY INDEX ROWID     | MT_PAY_RECORD_FIN |     1 |    44 |     1   (0)| 00:00:01 |
|   7 |        NESTED LOOPS                   |                   |    28 |  4704 |    28   (0)| 00:00:01 |
|   8 |         NESTED LOOPS                  |                   |    28 |  3472 |    22   (0)| 00:00:01 |
|   9 |          NESTED LOOPS                 |                   |    79 |  9638 |    21   (0)| 00:00:01 |
|  10 |           INLIST ITERATOR             |                   |       |       |            |          |
|  11 |            TABLE ACCESS BY INDEX ROWID| MT_BIZ_FIN        |    79 |  6952 |     6   (0)| 00:00:01 |
|* 12 |             INDEX RANGE SCAN          | IDX$$_429C0001    |    27 |       |     1   (0)| 00:00:01 |
|  13 |           TABLE ACCESS BY INDEX ROWID | BS_HOSPITAL       |     1 |    34 |     1   (0)| 00:00:01 |
|* 14 |            INDEX UNIQUE SCAN          | PK_BS_HOSPITAL    |     1 |       |     1   (0)| 00:00:01 |
|* 15 |          INDEX UNIQUE SCAN            | PK_BS_HOSP_LEVEL  |     1 |     2 |     1   (0)| 00:00:01 |
|* 16 |         INDEX RANGE SCAN              | IDX$$_429C0002    |     1 |       |     1   (0)| 00:00:01 |
|  17 |       TABLE ACCESS FULL               | BS_BIZTYPE        |    96 |  1728 |     3   (0)| 00:00:01 |
|* 18 |      INDEX RANGE SCAN                 | INX_BS_DISEASE_01 |     1 |    18 |     1   (0)| 00:00:01 |
|* 19 |     INDEX UNIQUE SCAN                 | PK_BS_INSURED     |     1 |     6 |     1   (0)| 00:00:01 |
|* 20 |    INDEX UNIQUE SCAN                  | PK_BS_CORP        |     1 |     5 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

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

   5 - access("A"."BIZ_TYPE"="F"."BIZ_TYPE" AND "A"."CENTER_ID"="F"."CENTER_ID")
   6 - filter("B"."VALID_FLAG"='1')
  12 - access("A"."IDCARD"='430703198202280017' AND "A"."VALID_FLAG"='1' AND ("A"."PERS_TYPE"='1'
              OR "A"."PERS_TYPE"='2'))
       filter("A"."BIZ_TYPE"='10' OR "A"."BIZ_TYPE"='11' OR "A"."BIZ_TYPE"='12' OR
              "A"."BIZ_TYPE"='13' OR "A"."BIZ_TYPE"='16' OR "A"."BIZ_TYPE"='17')
  14 - access("A"."HOSPITAL_ID"="D"."HOSPITAL_ID")
  15 - access("D"."HOSP_LEVEL"="E"."HOSP_LEVEL")
  16 - access("A"."HOSPITAL_ID"="B"."HOSPITAL_ID" AND "A"."SERIAL_NO"="B"."SERIAL_NO")
  18 - access("A"."CENTER_ID"="C"."CENTER_ID" AND "A"."FIN_DISEASE"="C"."ICD")
  19 - access("H"."INDI_ID"="A"."INDI_ID")
  20 - access("A"."CORP_ID"="G"."CORP_ID")

2- Using SQL Profile
--------------------
Plan hash value: 484693682

-----------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                   |   251 | 53965 |    25   (4)| 00:00:01 |
|   1 |  HASH GROUP BY                        |                   |   251 | 53965 |    25   (4)| 00:00:01 |
|*  2 |   TABLE ACCESS BY INDEX ROWID         | MT_PAY_RECORD_FIN |     1 |    44 |     1   (0)| 00:00:01 |
|   3 |    NESTED LOOPS                       |                   |   251 | 53965 |    24   (0)| 00:00:01 |
|   4 |     NESTED LOOPS                      |                   |    28 |  4788 |    19   (6)| 00:00:01 |
|   5 |      NESTED LOOPS                     |                   |    28 |  4284 |    18   (6)| 00:00:01 |
|   6 |       NESTED LOOPS                    |                   |    28 |  4116 |    17   (6)| 00:00:01 |
|   7 |        NESTED LOOPS                   |                   |    28 |  4060 |    16   (7)| 00:00:01 |
|   8 |         NESTED LOOPS                  |                   |    28 |  3108 |    10   (0)| 00:00:01 |
|*  9 |          HASH JOIN                    |                   |    28 |  2968 |     9   (0)| 00:00:01 |
|  10 |           TABLE ACCESS FULL           | BS_BIZTYPE        |    96 |  1728 |     3   (0)| 00:00:01 |
|  11 |           INLIST ITERATOR             |                   |       |       |            |          |
|  12 |            TABLE ACCESS BY INDEX ROWID| MT_BIZ_FIN        |    79 |  6952 |     6   (0)| 00:00:01 |
|* 13 |             INDEX RANGE SCAN          | IDX$$_429C0001    |    27 |       |     1   (0)| 00:00:01 |
|* 14 |          INDEX UNIQUE SCAN            | PK_BS_CORP        |     1 |     5 |     1   (0)| 00:00:01 |
|  15 |         TABLE ACCESS BY INDEX ROWID   | BS_HOSPITAL       |     1 |    34 |     1   (0)| 00:00:01 |
|* 16 |          INDEX UNIQUE SCAN            | PK_BS_HOSPITAL    |     1 |       |     1   (0)| 00:00:01 |
|* 17 |        INDEX UNIQUE SCAN              | PK_BS_HOSP_LEVEL  |     1 |     2 |     1   (0)| 00:00:01 |
|* 18 |       INDEX UNIQUE SCAN               | PK_BS_INSURED     |     1 |     6 |     1   (0)| 00:00:01 |
|* 19 |      INDEX RANGE SCAN                 | INX_BS_DISEASE_01 |     1 |    18 |     1   (0)| 00:00:01 |
|* 20 |     INDEX RANGE SCAN                  | IDX$$_429C0002    |     1 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

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

   2 - filter("B"."VALID_FLAG"='1')
   9 - access("A"."BIZ_TYPE"="F"."BIZ_TYPE" AND "A"."CENTER_ID"="F"."CENTER_ID")
  13 - access("A"."IDCARD"='430703198202280017' AND "A"."VALID_FLAG"='1' AND ("A"."PERS_TYPE"='1'
              OR "A"."PERS_TYPE"='2'))
       filter("A"."BIZ_TYPE"='10' OR "A"."BIZ_TYPE"='11' OR "A"."BIZ_TYPE"='12' OR
              "A"."BIZ_TYPE"='13' OR "A"."BIZ_TYPE"='16' OR "A"."BIZ_TYPE"='17')
  14 - access("A"."CORP_ID"="G"."CORP_ID")
  16 - access("A"."HOSPITAL_ID"="D"."HOSPITAL_ID")
  17 - access("D"."HOSP_LEVEL"="E"."HOSP_LEVEL")
  18 - access("H"."INDI_ID"="A"."INDI_ID")
  19 - access("A"."CENTER_ID"="C"."CENTER_ID" AND "A"."FIN_DISEASE"="C"."ICD")
  20 - access("A"."HOSPITAL_ID"="B"."HOSPITAL_ID" AND "A"."SERIAL_NO"="B"."SERIAL_NO")

-------------------------------------------------------------------------------

执行下面的语句来接受SQL 概要文件

SQL>  execute dbms_sqltune.accept_sql_profile(task_name =>'my_sql_tuning_task_2014080804', replace => TRUE,force_match => TRUE);

PL/SQL procedure successfully completed.

再来测试该语句

SQL> select * from  v_zzzd_ylbx_ylfymxcx where aac002='430703198202280017';

 AAC001 AAC002               AAB301  AKF008       AKF010   AKF011    AKF012   AKF013  AKF014 
------- -------------------- ------- -----------  -------- ------- -------- -------- ------- 
  44499 430703198202280017   430701  4307000305   18000304 购药          19       19       0 
  44499 430703198202280017   430701  4307030186   14200513 购药          34       34       0 
  44499 430703198202280017   430701  4307000070   11535710 购药           7        7       0 
  44499 430703198202280017   430701  4307000211   13157523 购药          10       10       0 
  44499 430703198202280017   430701  4307000178   10504509 购药        37.2     37.2       0 
  44499 430703198202280017   430701  4307000025   14186783 购药         6.5      6.5       0 
  44499 430703198202280017   430701  4307000211   18855092 购药          51       51       0 
  44499 430703198202280017   430701  4307000025   23298689 购药          32       32       0 
  44499 430703198202280017   430701  4307000305   17251025 购药          20       20       0 
  44499 430703198202280017   430701  4307000211   11246538 购药        10.5     10.5       0 
  44499 430703198202280017   430701  4307000011   20015343 门诊          20       20       0 
  44499 430703198202280017   430701  4307000135   13248044 购药       103.2    103.2       0 
  44499 430703198202280017   430701  4307000070   17745955 购药          20       20       0 
  44499 430703198202280017   430701  4307000011   23548511 门诊        94.2     94.2       0 
  44499 430703198202280017   430701  4307000305   18000319 购药          16       16       0 
  44499 430703198202280017   430701  4307000025   20291585 购药         374      374       0 
  44499 430703198202280017   430701  4307000075   11425923 购药        11.8     11.8       0 
  44499 430703198202280017   430701  4307000089   23298593 购药       170.8    170.8       0 
  44499 430703198202280017   430701  4307000110   11548588 购药        28.5     28.5       0 
  44499 430703198202280017   430701  4307000011   18454938 门诊       105.8    105.8       0 
  44499 430703198202280017   430701  4307000075   11757756 购药       282.7    282.7       0 
  44499 430703198202280017   430701  4307000025   10545113 购药       340.8    340.8       0 
  44499 430703198202280017   430701  4307000285   17325032 购药        67.5     67.5       0 
  44499 430703198202280017   430701  4307000070   17341126 购药          87       87       0 
  44499 430703198202280017   430701  4307000211   17655418 购药          20       20       0 
  44499 430703198202280017   430701  4307000011   19042114 门诊       127.2    127.2       0 
  44499 430703198202280017   430701  4307000211   18070864 购药           6        6       0 
  44499 430703198202280017   430701  4307000011   23547574 门诊          36       36       0 

28 rows selected.

Elapsed: 00:00:00.01

SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------
SQL_ID  1n2t3u0q0gmhz, child number 0
-------------------------------------
select * from  v_zzzd_ylbx_ylfymxcx where aac002='430703198202280017'

Plan hash value: 484693682

-----------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                   |       |       |    25 (100)|          |
|   1 |  HASH GROUP BY                        |                   |   251 | 53965 |    25   (4)| 00:00:01 |
|*  2 |   TABLE ACCESS BY INDEX ROWID         | MT_PAY_RECORD_FIN |     1 |    44 |     1   (0)| 00:00:01 |
|   3 |    NESTED LOOPS                       |                   |   251 | 53965 |    24   (0)| 00:00:01 |
|   4 |     NESTED LOOPS                      |                   |    28 |  4788 |    19   (6)| 00:00:01 |
|   5 |      NESTED LOOPS                     |                   |    28 |  4284 |    18   (6)| 00:00:01 |
|   6 |       NESTED LOOPS                    |                   |    28 |  4116 |    17   (6)| 00:00:01 |
|   7 |        NESTED LOOPS                   |                   |    28 |  4060 |    16   (7)| 00:00:01 |
|   8 |         NESTED LOOPS                  |                   |    28 |  3108 |    10   (0)| 00:00:01 |
|*  9 |          HASH JOIN                    |                   |    28 |  2968 |     9   (0)| 00:00:01 |
|  10 |           TABLE ACCESS FULL           | BS_BIZTYPE        |    96 |  1728 |     3   (0)| 00:00:01 |
|  11 |           INLIST ITERATOR             |                   |       |       |            |          |
|  12 |            TABLE ACCESS BY INDEX ROWID| MT_BIZ_FIN        |    79 |  6952 |     6   (0)| 00:00:01 |
|* 13 |             INDEX RANGE SCAN          | IDX$$_429C0001    |    27 |       |     1   (0)| 00:00:01 |
|* 14 |          INDEX UNIQUE SCAN            | PK_BS_CORP        |     1 |     5 |     1   (0)| 00:00:01 |
|  15 |         TABLE ACCESS BY INDEX ROWID   | BS_HOSPITAL       |     1 |    34 |     1   (0)| 00:00:01 |
|* 16 |          INDEX UNIQUE SCAN            | PK_BS_HOSPITAL    |     1 |       |     1   (0)| 00:00:01 |
|* 17 |        INDEX UNIQUE SCAN              | PK_BS_HOSP_LEVEL  |     1 |     2 |     1   (0)| 00:00:01 |
|* 18 |       INDEX UNIQUE SCAN               | PK_BS_INSURED     |     1 |     6 |     1   (0)| 00:00:01 |
|* 19 |      INDEX RANGE SCAN                 | INX_BS_DISEASE_01 |     1 |    18 |     1   (0)| 00:00:01 |
|* 20 |     INDEX RANGE SCAN                  | IDX$$_429C0002    |     1 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

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

   1 - SEL$F5BB74E1
   2 - SEL$F5BB74E1 / B@SEL$2
  10 - SEL$F5BB74E1 / F@SEL$2
  12 - SEL$F5BB74E1 / A@SEL$2
  13 - SEL$F5BB74E1 / A@SEL$2
  14 - SEL$F5BB74E1 / G@SEL$2
  15 - SEL$F5BB74E1 / D@SEL$2
  16 - SEL$F5BB74E1 / D@SEL$2
  17 - SEL$F5BB74E1 / E@SEL$2
  18 - SEL$F5BB74E1 / H@SEL$2
  19 - SEL$F5BB74E1 / C@SEL$2
  20 - SEL$F5BB74E1 / B@SEL$2

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

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('10.2.0.4')
      OPT_PARAM('optimizer_index_cost_adj' 20)
      OPT_PARAM('optimizer_index_caching' 90)
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$F5BB74E1")
      MERGE(@"SEL$2")
      OUTLINE(@"SEL$1")
      OUTLINE(@"SEL$2")
      FULL(@"SEL$F5BB74E1" "F"@"SEL$2")
      INDEX_RS_ASC(@"SEL$F5BB74E1" "A"@"SEL$2" ("MT_BIZ_FIN"."IDCARD" "MT_BIZ_FIN"."VALID_FLAG"
              "MT_BIZ_FIN"."PERS_TYPE" "MT_BIZ_FIN"."BIZ_TYPE"))
      NUM_INDEX_KEYS(@"SEL$F5BB74E1" "A"@"SEL$2" "IDX$$_429C0001" 3)
      INDEX(@"SEL$F5BB74E1" "G"@"SEL$2" ("BS_CORP"."CORP_ID"))
      INDEX_RS_ASC(@"SEL$F5BB74E1" "D"@"SEL$2" ("BS_HOSPITAL"."HOSPITAL_ID"))
      INDEX(@"SEL$F5BB74E1" "E"@"SEL$2" ("BS_HOSP_LEVEL"."HOSP_LEVEL"))
      INDEX(@"SEL$F5BB74E1" "H"@"SEL$2" ("BS_INSURED"."INDI_ID"))
      INDEX(@"SEL$F5BB74E1" "C"@"SEL$2" ("BS_DISEASE"."CENTER_ID" "BS_DISEASE"."ICD"))
      INDEX(@"SEL$F5BB74E1" "B"@"SEL$2" ("MT_PAY_RECORD_FIN"."HOSPITAL_ID"
              "MT_PAY_RECORD_FIN"."SERIAL_NO"))
      LEADING(@"SEL$F5BB74E1" "F"@"SEL$2" "A"@"SEL$2" "G"@"SEL$2" "D"@"SEL$2" "E"@"SEL$2" "H"@"SEL$2"
              "C"@"SEL$2" "B"@"SEL$2")
      USE_HASH(@"SEL$F5BB74E1" "A"@"SEL$2")
      USE_NL(@"SEL$F5BB74E1" "G"@"SEL$2")
      USE_NL(@"SEL$F5BB74E1" "D"@"SEL$2")
      USE_NL(@"SEL$F5BB74E1" "E"@"SEL$2")
      USE_NL(@"SEL$F5BB74E1" "H"@"SEL$2")
      USE_NL(@"SEL$F5BB74E1" "C"@"SEL$2")
      USE_NL(@"SEL$F5BB74E1" "B"@"SEL$2")
      END_OUTLINE_DATA
  */

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

   2 - filter("B"."VALID_FLAG"='1')
   9 - access("A"."BIZ_TYPE"="F"."BIZ_TYPE" AND "A"."CENTER_ID"="F"."CENTER_ID")
  13 - access("A"."IDCARD"='430703198202280017' AND "A"."VALID_FLAG"='1' AND (("A"."PERS_TYPE"='1'
              OR "A"."PERS_TYPE"='2')))
       filter(("A"."BIZ_TYPE"='10' OR "A"."BIZ_TYPE"='11' OR "A"."BIZ_TYPE"='12' OR
              "A"."BIZ_TYPE"='13' OR "A"."BIZ_TYPE"='16' OR "A"."BIZ_TYPE"='17'))
  14 - access("A"."CORP_ID"="G"."CORP_ID")
  16 - access("A"."HOSPITAL_ID"="D"."HOSPITAL_ID")
  17 - access("D"."HOSP_LEVEL"="E"."HOSP_LEVEL")
  18 - access("H"."INDI_ID"="A"."INDI_ID")
  19 - access("A"."CENTER_ID"="C"."CENTER_ID" AND "A"."FIN_DISEASE"="C"."ICD")
  20 - access("A"."HOSPITAL_ID"="B"."HOSPITAL_ID" AND "A"."SERIAL_NO"="B"."SERIAL_NO")

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

   1 - "A"."INDI_ID"[NUMBER,22], "A"."IDCARD"[VARCHAR2,25], "A"."CENTER_ID"[VARCHAR2,10],
       "A"."NAME"[VARCHAR2,20], "A"."HOSPITAL_ID"[VARCHAR2,20], "D"."HOSPITAL_NAME"[VARCHAR2,70],
       "A"."SERIAL_NO"[VARCHAR2,16], "F"."BIZ_NAME"[VARCHAR2,20], "A"."FIN_DATE"[DATE,7],
       "A"."IN_DAYS"[NUMBER,22], SUM("B"."REAL_PAY")[22], SUM(CASE "B"."FUND_ID" WHEN '003' THEN
       "B"."REAL_PAY" ELSE 0 END )[22], SUM(CASE "B"."FUND_ID" WHEN '001' THEN "B"."REAL_PAY" ELSE 0 END
       )[22], SUM(CASE  WHEN (("B"."FUND_ID"='999' OR "B"."FUND_ID"='003') AND
       ("B"."POLICY_ITEM_CODE"='S00' OR "B"."POLICY_ITEM_CODE"='S01' OR "B"."POLICY_ITEM_CODE"='C001' OR
       "B"."POLICY_ITEM_CODE"='C004''C006')) THEN "B"."REAL_PAY" ELSE 0 END )[22], SUM(CASE "B"."FUND_ID"
       WHEN '003' THEN "B"."REAL_PAY" WHEN '999' THEN "B"."REAL_PAY" ELSE 0 END )[22], SUM(CASE
       "B"."FUND_ID" WHEN '001' THEN "B"."REAL_PAY" WHEN '201' THEN "B"."REAL_PAY" WHEN '301' THEN
       "B"."REAL_PAY" ELSE 0 END )[22]
   2 - "B"."POLICY_ITEM_CODE"[VARCHAR2,20], "B"."FUND_ID"[VARCHAR2,3], "B"."REAL_PAY"[NUMBER,22]
   3 - "A"."CENTER_ID"[VARCHAR2,10], "F"."BIZ_NAME"[VARCHAR2,20], "A"."HOSPITAL_ID"[VARCHAR2,20],
       "A"."SERIAL_NO"[VARCHAR2,16], "A"."INDI_ID"[NUMBER,22], "A"."NAME"[VARCHAR2,20],
       "A"."IDCARD"[VARCHAR2,25], "A"."IN_DAYS"[NUMBER,22], "A"."FIN_DATE"[DATE,7],
       "D"."HOSPITAL_NAME"[VARCHAR2,70], "B".ROWID[ROWID,10]
   4 - "A"."CENTER_ID"[VARCHAR2,10], "F"."BIZ_NAME"[VARCHAR2,20], "A"."HOSPITAL_ID"[VARCHAR2,20],
       "A"."SERIAL_NO"[VARCHAR2,16], "A"."INDI_ID"[NUMBER,22], "A"."NAME"[VARCHAR2,20],
       "A"."IDCARD"[VARCHAR2,25], "A"."IN_DAYS"[NUMBER,22], "A"."FIN_DATE"[DATE,7],
       "D"."HOSPITAL_NAME"[VARCHAR2,70]
   5 - "A"."CENTER_ID"[VARCHAR2,10], "F"."BIZ_NAME"[VARCHAR2,20], "A"."HOSPITAL_ID"[VARCHAR2,20],
       "A"."SERIAL_NO"[VARCHAR2,16], "A"."INDI_ID"[NUMBER,22], "A"."NAME"[VARCHAR2,20],
       "A"."IDCARD"[VARCHAR2,25], "A"."IN_DAYS"[NUMBER,22], "A"."FIN_DISEASE"[VARCHAR2,20],
       "A"."FIN_DATE"[DATE,7], "D"."HOSPITAL_NAME"[VARCHAR2,70]
   6 - "A"."CENTER_ID"[VARCHAR2,10], "F"."BIZ_NAME"[VARCHAR2,20], "A"."HOSPITAL_ID"[VARCHAR2,20],
       "A"."SERIAL_NO"[VARCHAR2,16], "A"."INDI_ID"[NUMBER,22], "A"."NAME"[VARCHAR2,20],
       "A"."IDCARD"[VARCHAR2,25], "A"."IN_DAYS"[NUMBER,22], "A"."FIN_DISEASE"[VARCHAR2,20],
       "A"."FIN_DATE"[DATE,7], "D"."HOSPITAL_NAME"[VARCHAR2,70]
   7 - "A"."CENTER_ID"[VARCHAR2,10], "F"."BIZ_NAME"[VARCHAR2,20], "A"."HOSPITAL_ID"[VARCHAR2,20],
       "A"."SERIAL_NO"[VARCHAR2,16], "A"."INDI_ID"[NUMBER,22], "A"."NAME"[VARCHAR2,20],
       "A"."IDCARD"[VARCHAR2,25], "A"."IN_DAYS"[NUMBER,22], "A"."FIN_DISEASE"[VARCHAR2,20],
       "A"."FIN_DATE"[DATE,7], "D"."HOSPITAL_NAME"[VARCHAR2,70], "D"."HOSP_LEVEL"[CHARACTER,1]
   8 - "A"."CENTER_ID"[VARCHAR2,10], "F"."BIZ_NAME"[VARCHAR2,20], "A"."HOSPITAL_ID"[VARCHAR2,20],
       "A"."SERIAL_NO"[VARCHAR2,16], "A"."INDI_ID"[NUMBER,22], "A"."NAME"[VARCHAR2,20],
       "A"."IDCARD"[VARCHAR2,25], "A"."IN_DAYS"[NUMBER,22], "A"."FIN_DISEASE"[VARCHAR2,20],
       "A"."FIN_DATE"[DATE,7]
   9 - (#keys=2) "A"."CENTER_ID"[VARCHAR2,10], "F"."BIZ_NAME"[VARCHAR2,20],
       "A"."HOSPITAL_ID"[VARCHAR2,20], "A"."SERIAL_NO"[VARCHAR2,16], "A"."INDI_ID"[NUMBER,22],
       "A"."NAME"[VARCHAR2,20], "A"."IDCARD"[VARCHAR2,25], "A"."CORP_ID"[NUMBER,22],
       "A"."IN_DAYS"[NUMBER,22], "A"."FIN_DISEASE"[VARCHAR2,20], "A"."FIN_DATE"[DATE,7]
  10 - "F"."CENTER_ID"[VARCHAR2,10], "F"."BIZ_TYPE"[CHARACTER,2], "F"."BIZ_NAME"[VARCHAR2,20]
  11 - "A"."HOSPITAL_ID"[VARCHAR2,20], "A"."SERIAL_NO"[VARCHAR2,16], "A"."BIZ_TYPE"[VARCHAR2,2],
       "A"."INDI_ID"[NUMBER,22], "A"."NAME"[VARCHAR2,20], "A"."IDCARD"[VARCHAR2,25],
       "A"."CORP_ID"[NUMBER,22], "A"."IN_DAYS"[NUMBER,22], "A"."FIN_DISEASE"[VARCHAR2,20],
       "A"."FIN_DATE"[DATE,7], "A"."CENTER_ID"[VARCHAR2,10]
  12 - "A"."HOSPITAL_ID"[VARCHAR2,20], "A"."SERIAL_NO"[VARCHAR2,16], "A"."BIZ_TYPE"[VARCHAR2,2],
       "A"."INDI_ID"[NUMBER,22], "A"."NAME"[VARCHAR2,20], "A"."PERS_TYPE"[VARCHAR2,3],
       "A"."IDCARD"[VARCHAR2,25], "A"."CORP_ID"[NUMBER,22], "A"."IN_DAYS"[NUMBER,22],
       "A"."FIN_DISEASE"[VARCHAR2,20], "A"."FIN_DATE"[DATE,7], "A"."CENTER_ID"[VARCHAR2,10]
  13 - "A".ROWID[ROWID,10], "A"."IDCARD"[VARCHAR2,25], "A"."PERS_TYPE"[VARCHAR2,3],
       "A"."BIZ_TYPE"[VARCHAR2,2]
  15 - "D"."HOSPITAL_NAME"[VARCHAR2,70], "D"."HOSP_LEVEL"[CHARACTER,1]
  16 - "D".ROWID[ROWID,10]
  20 - "B".ROWID[ROWID,10]

Note
-----
   - SQL profile "SYS_SQLPROF_0151ed60f3d28000" used for this statement


163 rows selected.

从SQL profile “SYS_SQLPROF_0151ed60f3d28000” used for this statement 这个信息就是知道已经使用了SQL概要文件
现在语句执行只要0.1毫秒

收集优化统计数据(Optimizer Statistics)的最佳实践方法

介绍
oracle优化器对一个sql语句检测所有可能的执行计划并选择一个成本值最小的,这里的成本代表了一个特定执行计划的资源使用情况.为了让优化器能准确的判断一个执行计划的成本它必须要关于sql语句要访问的所有对象(表或索引)的信息同时还要有运行sql语句的系统信息.

这些必要的信息通常称为优化器统计信息.理解和管理优化器统计信息是优化sql执行的关键.知道何时以及如何收集统计信息对于维护可以接受的性能来说至关重要.

这里将介绍在常见的oracle数据库场景中何时以及如何来收集统计信息.它包含以下内容:
怎样收集统计信息
何时收集统计信息
提高收集统计信息的效率
何时不收集统计信息
收集处理的统计信息

怎样收集统计信息
在oracle中收集统计信息的首选方法是使用提供了自动统计信息收集job.

自动统计信息收集job
对运行oracle autotask任务的一个预定义维护窗口期间对哪些丢失统计信息或统计信息过期的所有数据库对象收集统计信息,oracle内部很重视数据库对象的统计信息因此这此对象在进行处理前需要更新统计信息.自动统计信息收集job是使用dbms_stats.gather_database_stats_job_proc过程来实现的,它与dbms_stats.gather_*_stats过程使用相同的缺省参数.这个缺省值在大多数情况下是有效的.然而偶尔也需要改变这些统计信息收集参数的缺省值,可以通过dbms_stats.set_*_pref过程来进行修改.例如设置一个表中有5%的数据发生了改变而不是缺省值10%时就认会它的统计信息失效了.如果想要改变这个统计信息失效的阈值,可以使用dbms_stats.set_table_prefs过程来修改stale_percent参数.

sys@JINGYONG> begin
  2  dbms_stats.set_table_prefs('SH','SALES','STALE_PERCENT','5');
  3  end;
  4  /

PL/SQL 过程已成功完成。

使用dbms_stats.set_table_prefs过程将表统计信息失效的阈stale_percent改变了5%.

手动统计信息收集
如果已经有一个完善的统计信息收集过程或者因为某些原因想要对特定用户方案禁用自动统计信息收集而只保留收集数据字典的统计信息.可以使用dbms_stats.set_global_prefs过程来改变autostats_target参数为oracle来替代auto.

sys@JINGYONG> begin
  2  dbms_stats.set_global_prefs('AUTOSTATS_TARGET','ORACLE');
  3  end;
  4  /

PL/SQL 过程已成功完成。

用上面的代码改变自动统计信息收集job只自动收集数据字典统计信息.

为了手动收集统计信息你应该使用dbms_stats包,用它来替找过时的analyze命令.dbms_stats包提供多个
dbms_stats.gather_*_stats过程来收集用户方案对象,数据字典和固定对象的统计信息.理想情况下应该让这些过程除了用户方案名和对象名参数之外其它的参数值都使用缺省值.缺省参数值和自适应参数设置在大多数情况下已经足够了.

sys@JINGYONG> begin
  2  dbms_stats.gather_table_stats('SH','SALES');
  3  end;
  4  /

PL/SQL 过程已成功完成。

两个修改最频繁的参数是ESTIMATE_PERCENT和METHOD_OPT

ESTIMATE_PERCENT
在收集统计信息的过程最常见的问题是’使用什么样的抽样大小’与这个问题相关的设置是dbms_stats.gather_*_stats过程中的ESTIMATE_PERCENT参数.这个ESTIMATE_PERCENT参数判断用来计算统计信息所使用的行数百份比.当表中的所有行被处理时收集的统计信息是最准确的(比如100%抽样).然而抽样的样本越大收集操作的时间越长.因此使用怎样的抽样大小来提供及时准确的统计信息.

oracle11G之前的ESTIMATE_PERCENT
在oracle10g中,ESTIMATE_PERCENT的缺省值从100%变成了AUTO_SAMPLE_SIZE.这个AUTO_SAMPLE_SIZE的目的是让oracle在每次收集统计信息时来判断每一个表的合适的抽样大小.这将允许oracle自动地对每一个表改变其抽样大小但仍然能确保及时有效的收集统计信息.这种方法对于大多数表来说是一种可取的方法但是对于数据存在倾斜的表来说存在问题.当表中数据出现倾斜AUTO_SAMPLE_SIZE算法通常选择的抽样大小太小,在这种情况下最好的方法还是手动指定ESTIMATE_PERCENT参数的大小.

oracle11g中的ESTIMATE_PERCENT
oracle11g中引入一种新的hash-based抽样算法来提供精确的统计数据解决了精确和速度两个关键问题.它的精确度接近100%抽样大小的水平但是成本与10%抽样大小相当.这种新的算法只有当任何dbms_stats.gather_*_stats过程中的ESTIMATE_PERCENT参数设置为AUTO_SAMPLE_SIZE时才会使用.

下面的表格显示了一个早前使用1%抽样,100%抽样和AUTO_SAMPLE_SIZE抽样收集统计信息的结查.第一行比较运行的时间,后继的行将显示每次运行计算出来的L_ORDERKDY和L_COMMENT两个列不同值的数量(NDV)

-----------------------------------------------------------------------------------------------
                                 1% sample      auto_sample_size      100% sample
-----------------------------------------------------------------------------------------------
Elapse time (sec)                 797                 1908             18772
NDV for L_ORDERKEY Column       225000000         450000000            450000000
NDV for L_COMMENT Column        7244885           177499684            181122127
-----------------------------------------------------------------------------------------------

在这种情况下新的auto_sample_size算法比100%抽样执行的时间要快9倍且只比1%抽样执行的时间慢2.4倍,而提供的统计信息质量几乎接近100%抽样的水平(不足以改变执行计划).

从oracle11g开始强烈建议你使用estimate_percent参数的缺省值.如果你手动设置estimate_percent参数尽管你将它设置为100%它还是会使用旧的收集算法.
method_opt
在dbms_stats.gather_*_stats过程中到目前为止最有争议的参数就是method_opt.这个method_opt参数控制着在收集统计信息过程是否创建直方图.直方图是一种特殊类型的列统计信息提供关于表中列数据分布的详细信息.所以这就是为什么直方图存在争议的问题

直方图的创建会增加统计收集的时间和系统资源的使用但最大的问题是直方图与bind peeking功能的相互作用以及对near popular values基数评估的影响.

直方图与bind peeking
bind peeking对直方图的不利影响已经在oracle11g中通过引入自适应游标共享被淡化了但是直到今天仍然感受到它的影响.为了说明自适应游标共享是怎样解决这个问题的先来检查一个这个问题的原因.

oracle11g之前的直方图与bind peeking
在oracle11g之前当优化一个在where子句中包含绑定变量的语句时优化在第一次执行这个语句时会窥视这些绑定变量的值(在硬解析阶段).优化器然后会基于这个初始化的绑定变量值来判断执行计划.对于后续执行这个查询不会执行绑定变量窥视(没有硬解析发生),所以对于后面的这个语句的所有执行即使绑定变量发生了改变也会决定使用第一次设置的绑定变量值所产生的执行计划对于在表达式中使用绑定变量的列存在直方图这将有助于判断初始化绑定变量值的最优执行计划.因此对于相同的语句依赖于初始化硬解析时绑定变量的值执行计划可能会有所不同.

有两种方法可以避免这个问题:删除直方图并在将业停止收集直方图或禁用bind peeking绑定变量窥视.根据所有的sql是否都在使用绑定变量你可以判断哪一种方法更适合你的数据库.

禁止直方图的创建
如果你的环境中所有sql语句使用了绑定变量那么最好是删除存在的直方图并在将来的收集统计信息过程中禁止创建直方图.禁上直方图的创建能确保依赖于绑定变量的值的执行计划不会发生改变也会减少收集统计信息的时间.没有直方图优化器会假设列中不相同的值是均匀分布在所有行中的并当窥视sql语句中初始化绑定变量值时使用NDV(number distinct values)来判断基数的评估.

可以使用dbms_stats.delete_table_stats过程来删除统计信息中存在的直方图信息.

sys@JINGYONG> begin
  2  dbms_stats.delete_table_stats('SH','SALES');
  3  end;
  4  /

PL/SQL 过程已成功完成。

接下来可以通过使用dbms_stats.set_param过程来改变method_opt参数的缺省值来阻止将来生成直方图.这能确保
dbms_stats.gather_*_stats过程和自动统计信息收集job在将来都不会收集直方图信息.

sys@JINGYONG> begin
  2  dbms_stats.set_param(pname=>'METHOD_OPT',pval=>'FOR ALL COLUMNS SIZE 1');
  3  end;
  4  /

PL/SQL 过程已成功完成。

最后可以使用dbms_stats.gather_table_stats过程来对受影响的对象重新收集统计信息.

注意在oracle11g中通过使用dbms_stats.delete_column_stats和对直方图设置col_stat_type可以删除不想要的直方图而不用删除所有的直方图信息.也可以对单个表或者使用dbms_stats.set_table_prefs过程来对列禁止直方图的创建.你知道直方图也用于某些连接谓词而且删除直方图对连接谓词的基数评估会有影响.在这种情况下更安全的方法是禁用绑定变量窥视.

禁用绑定变量窥视
如果你的环境中有一些sql语句是使用绑定变量而有一些sql语句使用了literal values那么你应该禁用绑定变量窥视.通过禁用绑定变量窥视它将阻止优化器窥初始绑定变量值且将不使用直方图来进行基数评估.相反优化器将列中的不相同值是均匀分布在行中并使用NDV(number distinct values)来进行基数评估操作.这将对于使用绑定变量的语句使用一致的执行计划.但是如果sql语句使用literal values那么仍然能利用直方图来得到最优的执行计划.可以通过设置隐含参数_optim_peek_user_binds为false来禁用绑定变量窥视.

oracle11g中的直方图与绑定变量窥视
在oracle11g中优化器已经增强了允许多个版本的执行计划用于使用绑定变量的单个sql语句.这个功能就叫作自适应游标共享且依赖于对执行统计的监控来确保每一个绑定变量值使用正确的执行路径.

在第一次执行时优化器将窥视绑定变量值且基于绑定变量值的选择性来判断其执行计划,与oracle11g之前的版本一样.如果优化器认为最佳的执行计划可能依赖于绑定变量的值(例如,列上的直方图或者一个范围谓词,or,< ,>)这个游标将会被标记为bind sensitive.当一个游标被标记为bind sensitive.oracle将监控游标使用不同绑定值的行为来确定是否要使用一个不同的执行计划.

如果一个不同的绑定变量值在后继的执行中使用,优化器将使用相同的执行计划因为oracle一开始会假设游标能被共享.然而新的

绑定变量的执行统计会被记录并与之前绑定变量值的执行统计进行比较.如果oracle判断新的绑定变量值造成了操作的数据量明显不同那么对于新的绑定变量值在下一次执行时会进行硬解析且这个游标会被标记为bind-aware.每一个bind_aware游标与绑定变量的选择性范围有关因此游标只有在这个语句的绑定变量值在一个被认为可共享的范围之内才能被共享.

当另一个新的绑定变量值被使用时,优化器将会基于绑定变量值的选择性的相似度来找到一个它认为最好的一个游标.如果它不能找到一个游标,它将创建一个新的.如果执行计划的一个新的游标与一个已经存在的游标一样,那么两个游标将会在共享池中合并从而节省空间.游标的选择性范围为了包含新绑定变量值的选择性将会有所增加.

通过允许对单个sql语句存在多个执行计划,在oracle11g中直方图对于使用绑定变量的语句不再有负面影响.

直方图和near popular values
当优化器遇到一个where子句中谓词列上有直方图,它将基于literal value的出现频率来进行基数评估.例如假设在sh用户下的customers表中的cust_city_di列上有一个高度平衡的直方图且有一个使用cust_city_id=51806的查询.优化器首先会检查这个直方图有51806作为它的end point有多少个桶.在这种情况下,endpint是51806的桶有136,137,138和139(可以查看user_histograms).因为endpoint的值有两个或多个桶要被考虑为出现频繁的优化器将使用下面的公式来进行基数评估:
(Number of bucket endpoints / total number of buckets) * number of rows in the table
在这种情况下:4/254*55500=874

sys@JINGYONG> set autotrace traceonly
sys@JINGYONG> select count(*) from sh.customers where cust_city_id=51806;


执行计划
----------------------------------------------------------
Plan hash value: 296924608

--------------------------------------------------------------------------------

| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |           |     1 |     5 |   382   (3)| 00:00:04 |

|   1 |  SORT AGGREGATE    |           |     1 |     5 |            |          |

|*  2 |   TABLE ACCESS FULL| CUSTOMERS |   874 |  4370 |   382   (3)| 00:00:04 |

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

   2 - filter("CUST_CITY_ID"=51806)

然而如果谓词是cust_city_id=52500,它对于任何桶来说都不是一个endpoint那么优化器会使用一个同的公式来进行基数评估.对于endpoint值只在一个桶出现或者任何桶中都没有这个endpoint时优化器会使用下面的计算公式:
density * number of rows in the table,

density的值可以在user_tab_col_statistics中看到,它的值从oracle10.2.0.4以后优化器将不再使用.记录这个值是为了向后兼容,在oracle9i和oracle10g前期的版本中会使用这个值.此外如果参数optimizer_features_enable设置的版本小于10.2.0.4,那么视图中的density仍然会被使用.

sys@JINGYONG> select column_name,density from dba_tab_col_statistics where owner
='SH' and table_name='CUSTOMERS';

COLUMN_NAME                       DENSITY
------------------------------ ----------
CUST_ID                        .000018018
CUST_FIRST_NAME                .000769231
CUST_LAST_NAME                 .001101322
CUST_GENDER                            .5
CUST_YEAR_OF_BIRTH             .013333333
CUST_MARITAL_STATUS            .090909091
CUST_STREET_ADDRESS            .000019629
CUST_POSTAL_CODE               .001605136
CUST_CITY                      .001612903
CUST_CITY_ID                   .002179391
CUST_STATE_PROVINCE            .006896552
CUST_STATE_PROVINCE_ID         .000009009
COUNTRY_ID                     .000009009
CUST_MAIN_PHONE_NUMBER         .000019608
CUST_INCOME_LEVEL              .083333333
CUST_CREDIT_LIMIT                    .125
CUST_EMAIL                     .000588582
CUST_TOTAL                              1
CUST_TOTAL_ID                  .000009009
CUST_SRC_ID                             0
CUST_EFF_FROM                           1
CUST_EFF_TO                             0
CUST_VALID                             .5

已选择23行。

sys@JINGYONG> select column_name,num_buckets,histogram from dba_tab_col_statisti
cs where owner='SH' and table_name='CUSTOMERS' and column_name='CUST_CITY_ID';

COLUMN_NAME                    NUM_BUCKETS HISTOGRAM
------------------------------ ----------- ---------------
CUST_CITY_ID                           254 HEIGHT BALANCED


sys@JINGYONG> show parameter optimzer_features_enable
sys@JINGYONG> show parameter optimizer_features_enable

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_features_enable            string      11.2.0.1
sys@JINGYONG> set autotrace traceonly
sys@JINGYONG> select count(*) from sh.customers where cust_city_id=52500;


执行计划
----------------------------------------------------------
Plan hash value: 296924608

--------------------------------------------------------------------------------

| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |           |     1 |     5 |   382   (3)| 00:00:04 |

|   1 |  SORT AGGREGATE    |           |     1 |     5 |            |          |

|*  2 |   TABLE ACCESS FULL| CUSTOMERS |    66 |   330 |   382   (3)| 00:00:04 |

--------------------------------------------------------------------------------


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

   2 - filter("CUST_CITY_ID"=52500)

现在将optimizer_features_enable设置为10.2.0.3

sys@JINGYONG> alter session set optimizer_features_enable='10.2.0.3';

会话已更改。

sys@JINGYONG> set autotrace traceonly
sys@JINGYONG> select count(*) from sh.customers where cust_city_id=52500;


执行计划
----------------------------------------------------------
Plan hash value: 296924608

--------------------------------------------------------------------------------

| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |           |     1 |     5 |   382   (3)| 00:00:04 |

|   1 |  SORT AGGREGATE    |           |     1 |     5 |            |          |

|*  2 |   TABLE ACCESS FULL| CUSTOMERS |   121 |   605 |   382   (3)| 00:00:04 |

--------------------------------------------------------------------------------


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

   2 - filter("CUST_CITY_ID"=52500)

现在的基数是121=55500*.002179391,CUST_CITY_ID的density为.002179391

这些nearly popular值被归类为non-popular values使用与non-popular values相同的计算公式.例如,如果谓词是
cust_city_id=52114,那么它的评估基数将是66行.与non-popular值52500的基数一样,但是cust_city_id=52114实际上有227行记录.

sys@JINGYONG> select count(*) from sh.customers where cust_city_id=52114;


执行计划
----------------------------------------------------------
Plan hash value: 296924608

--------------------------------------------------------------------------------

| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |           |     1 |     5 |   382   (3)| 00:00:04 |

|   1 |  SORT AGGREGATE    |           |     1 |     5 |            |          |

|*  2 |   TABLE ACCESS FULL| CUSTOMERS |    66 |   330 |   382   (3)| 00:00:04 |

--------------------------------------------------------------------------------


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

   2 - filter("CUST_CITY_ID"=52114)

sys@JINGYONG> select count(*) from sh.customers where cust_city_id=52114;

  COUNT(*)
----------
       227

唯一能让优化器意识到这些near popular values的方法是使用动态抽样.动态抽样在优化一个sql语句时会收集额外的statement-specific对象统计信息.在这个例子中,动态抽样提示加入到了查询中且优化器会得到一个更准确的基数评估值.

sys@JINGYONG> select /*+ dynamic_sampling(a 2) */ count(a.cust_id) from sh.custo
mers a where a.cust_city_id=52114;


执行计划
----------------------------------------------------------
Plan hash value: 296924608

--------------------------------------------------------------------------------

| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |           |     1 |     5 |   382   (3)| 00:00:04 |

|   1 |  SORT AGGREGATE    |           |     1 |     5 |            |          |

|*  2 |   TABLE ACCESS FULL| CUSTOMERS |    246 |   410 |   382   (3)| 00:00:04 |

--------------------------------------------------------------------------------


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

   2 - filter("A"."CUST_CITY_ID"=52114)

Note
-----
   - dynamic sampling used for this statement (level=2)

使用动态抽样可以提高高度平衡直方图中non-popular value的基数评估

在上面已经讨论了在oracle10g中使用直方图可能存的问题和可能的解决方法.建议从oracle11g开始收集统计信息时使用参数METHOD_OPT的缺省值且利用自适应游标.

如果想手动设置method_opt参数值不使用缺省值要确保只对需要直方图的列进行设置.将method_opt设置为for all columns size 254将会使oracle对每一个列都收集直方图信息.这对于收集统计信息来说是不必要的会增加运行时间和浪费系统资源,也会增加存储这些统计信息的空间.

还要避免将method_opt设置为for all index columns size 254它使oracle对存过索引的每一个列收集直方图信息,也会浪费系统资源.这个设置还有一个副作用就是会阻止oracle对哪些不存在索引的列收集基本的列统计信息.

pending statistics
当决定改变dbms_stats_gather_*_stats过程的参数缺省值时强烈建议在生产系统中修改之前先验证这些改变.如果没有一个完整的测试环境应该使用pending statistics.使用pending statistics代替常用的数据字典表,存储在pending表中的统计信息在它们被发和被系统使用之前可以以一种受控的方式来启用和测试.为了激活pending统计信息的收集需要对希望创建pending统计信息的对象使用dbms_stats.set_*_prefs过程将参数publish从缺省值true改变false.下面的例子中对sh用户下的sales表启用pending统计信息并对sales表收集统计信息.

sys@JINGYONG> begin
  2  dbms_stats.set_table_prefs('SH','SALES','PUBLISH','FALSE');
  3  end;
  4  /

PL/SQL 过程已成功完成。

通过将publish设置为false来启用pending统计信息

正常的收集对象统计信息

sys@JINGYONG> begin
  2  dbms_stats.gather_table_stats('SH','SALES');
  3  end;
  4  /

PL/SQL 过程已成功完成。

对于这些对象收集的统计信息可以查询*_tab_pending_stats视图来显示:

sys@JINGYONG>select * from dba_tab_pending_stats where owner='SH';
 
OWNER                          TABLE_NAME                     PARTITION_NAME                 SUBPARTITION_NAME                NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
------------------------------ ------------------------------ ------------------------------ ------------------------------ ---------- ---------- ----------- ----------- -------------
SH                             SALES                                                                                            918843       1769          29      918843 2013-12-19 9:
SH                             SALES                          SALES_1995                                                             0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_1996                                                             0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_H1_1997                                                          0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_H2_1997                                                          0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_Q1_1998                                                      43687         90          29       43687 2013-12-19 9:
SH                             SALES                          SALES_Q1_1999                                                      64186        121          29       64186 2013-12-19 9:
SH                             SALES                          SALES_Q1_2000                                                      62197        119          29       62197 2013-12-19 9:
SH                             SALES                          SALES_Q1_2001                                                      60608        119          30       60608 2013-12-19 9:
SH                             SALES                          SALES_Q1_2002                                                          0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_Q1_2003                                                          0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_Q2_1998                                                      35758         76          29       35758 2013-12-19 9:
SH                             SALES                          SALES_Q2_1999                                                      54233        103          29       54233 2013-12-19 9:
SH                             SALES                          SALES_Q2_2000                                                      55515        109          30       55515 2013-12-19 9:
SH                             SALES                          SALES_Q2_2001                                                      63292        119          30       63292 2013-12-19 9:
SH                             SALES                          SALES_Q2_2002                                                          0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_Q2_2003                                                          0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_Q3_1998                                                      50515         95          29       50515 2013-12-19 9:
SH                             SALES                          SALES_Q3_1999                                                      67138        120          29       67138 2013-12-19 9:
SH                             SALES                          SALES_Q3_2000                                                      58950        110          30       58950 2013-12-19 9:
SH                             SALES                          SALES_Q3_2001                                                      65769        124          29       65769 2013-12-19 9:
SH                             SALES                          SALES_Q3_2002                                                          0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_Q3_2003                                                          0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_Q4_1998                                                      48874        108          29       48874 2013-12-19 9:
SH                             SALES                          SALES_Q4_1999                                                      62388        114          29       62388 2013-12-19 9:
SH                             SALES                          SALES_Q4_2000                                                      55984        106          30       55984 2013-12-19 9:
SH                             SALES                          SALES_Q4_2001                                                      69749        136          29       69749 2013-12-19 9:
SH                             SALES                          SALES_Q4_2002                                                          0          0           0           0 2013-12-19 9:
SH                             SALES                          SALES_Q4_2003                                                          0          0           0           0 2013-12-19 9:
 
29 rows selected
 

可以通过一个alter session命令来设置初始化参数optimizer_use_pending_stats为true来使用pending统计信息.在启用pending统计信息之后任何在该会话运行的sql将使用这些新的没有发布的统计信息.对于在工作负载下的所访问的表没有pending统计信息时优化器将使用标准数据字典表中的当前统计信息.当你验证这些pending统计信息后可以使用dbms_stats.publish_pending_stats过程来发布.

何时收集统计信息
为了选择一个最佳的执行计划优化器必须要有有代表性的统计信息,有代表性的统计信息并不是最新的统计信息但是这组统计信息能帮助优化器判断在执行计划中每一个操作步骤所期待的正确的行记录数.

自动统计信息收集job
在一个预定义的维护窗口中oracle会自动对哪些丢失统计信息或者统计信息失效的所有对象收集统计信息(每个工作日的晚上10点到零晨2点和每个周末的6点到零晨2点).

可以使用企业管理器或使用dbms_scheduler和dbms_auto_task_admin包来改变这个维护窗口.

如果已经有一个完善的统计信息收集过程或者如果因为某些原因想要禁用自动统计信息收集可以禁用收集任务:

sys@JINGYONG> begin
  2  dbms_auto_task_admin.disable(
  3  client_name=>'auto optimizer stats collection',
  4  operation=>null,
  5  window_name=>null);
  6  end;
  7  /

PL/SQL 过程已成功完成。

手动统计信息收集
如果计划手动维护优化器统计信息将需要判断何时进行收集.

基于失效统计,自动收集job或者系统中加载新数据的时间你能判断何时来收集统计信息.如果基本数据没有发生明显的改变不建议不断的重新收集统计信息这样只会浪费系统资源.

如果数据在一个预定义的ETL或ELT job只加载到系统中那么统计信息收集操作应该作为这个过程的一部分被调度.注意如果使用分区交换加载并希望利用增量统计信息将需要在交换过程完成后收集统计信息.

然而如果系统中有大量的联机事务只插入少量的数据但是这些操作每天都会发生,你将需要判断何时你的统计信息将会失效然后触发统计信息收集job.如果你计划依赖user_tab_statistics中的stale_stats列来判断统计信息是否失效你应该能意识到这些信息每天及时更新.如果需要更多更及时的信息比如你的表什么时候执行过DML操作你将需要查看user_tab_modifications视图,它会显示每一个表上执行的insert,update,delete操作,表是否执行过truncated并计算自己是否已经失效.需要注意这些信息是否定时的从内存中自动更新.如果需要最新的信息需要使用dbms_stats.flush_database_monitoring_info函数来手动刷新.

阻止超出范围的条件
不管你是使用自动统计信息收集job还是手动收集统计信息,如果终端用户在统计信息收集之前开始查询新插入的数据,即使只有不到10%的数据发生了变化也可能由于失效的统计信息得到一个次优的执行计划.发生这种问题最常见的原因是where子句中谓词提供的值超出了最小/最大列统计信息所能表示的范围.这通常称为超出范围的错误.

这种情况在分区表中很常见.一个新分区刚添加到一个存在的范围分区表中且记录刚被插入到分区中.在对这个新分区收集统计信息之前终端用户就开始查询这些新的数据.对于分区表,可以使用dbms_stats.copy_table_stats过程(从oracle10.2.0.4开始可以使用)来阻止超出范围的条件表达式.这个过程将复制原分区数据的统计信息为新创建分区的统计信息.它能复制依赖对象的统计信息:列,本地(分区)索引等等.直到对分区收集统计信息之前复制的统计信息只能作为临时的解决方法来使用.复制的统计信息不能代替真实收集的统计信息.

注意通常dbms_stats.copy_table_stats只能调整分区统计信息不能调整全局或表级别的统计信息.如果想在复制统计信息时对分区列进行全局级别的更新需要将dbms_stats.copy_table_stats中的flags参数设置为8.

对于非分区表你能通过dbms_stats.set_column_stats过程来手动设置列的最大值.通常这种方法不建议它并不能代替真实的收集的统计信息.

提高收集统计信息的效率
随着数据量的增长和维护窗口的缩短能及时的收集统计信息是很重要的.oracle提供了多种方法来提高统计信息收集的速度.使用并行用于收集统计信息的几种并行方法
内部对象并行
外部对象并行
内部对象并行与外部对象并行的组合

内部对象并行
内部对象并行是由dbms_stats.gather_*_stats过程的degree参数来控制的.degree参数控制着用于收集统计信息的并行服务器进程的数量.

通常oracle使用数据字典表中并行属性的值作为指定并行服务器进程的参数值.在oracle数据库中所有的表都有一个degree属性缺省值为1.对要收集统计信息的大表设显示地设置这个参数能提高统计信息收集的速度.

你也可以设置degree为auto_degree.oracle将基于一个对象的大小自动判断一个合适的并行服务进程个数来收集统计信息.这个值的范围在1-小对象(串行操作)到大对象的default_degree((PARALLEL_THREADS_PER_CPU X CPU_COUNT)之间.

你将会注意到对一个分区表设置degree这意味着对每一个分区使用多个并行服务器进程来收集统计信息但是不能同时对不同的分区收集统计信息.统计信息只能在一个分区收集完之后才能收集下一个分区.

外部对象并行
在oracle11.2.0.2中,外部对象并行被引入且由global statistics gathering preference concurrent来控制.当concurrent设置为true时,oracle将使用oracle job作业调度和高级队列组来创建和管理多个统计信息收集job并发执行.通过oracle来完全利用多个cpu来对多个表和(子)分区并发的收集统计信息来减小整个统计信息收集的时间.

活动并行统计信息收集job的最大个数是由job_queue_processes参数来控制的.job_queue_processes缺省值设置为1000.这通常对于并行统计信息收集操作来说太高了尤其是在并行执行也在使用时更是如此.一个最有效的值应该是总cpu核数的2倍(在rac中这是每一个节点的参数值).你需要确在系统级别设置这个参数(alter system命令或init.ora文件)而不是在会话级别(alter session).

内部和外部并行的组合
在一个并行统计收集操作中的每一个统计信息收集job都能以并行的方式来执行.将并行统计收集和并行执行组合起来能大大减小收集统计信息的时间.

当使用并行执行作为一个并行统计信息收集操作的一部分时你应该禁用parallel_adaptive_multi_user初始化参数来阻止并行job被降级为串行操作.它应该在系统级别来禁用而不是在会话级别禁用这个参数:

sys@JINGYONG> alter system set parallel_adaptive_multi_user=false;

系统已更改。

增量统计信息
分区表的统计信息收集是由表级别(global statistics)和(子)分区级别的统计信息收集操作组成的.如果一个分区表的incremental preference设置为true,dbms_stats.gather_*_stats中参数granularity的值包含global和estimate_percent设置为auto_sample_size,oracle将会通过扫描这些已经被添加或被修改的分区来获得全局级别的统计信息而不是整个表的.

增量全局统计信息是由表中每个分区存储的概要计算出来的.一个概要是这个分区和分区中列的统计信息的元数据.聚合分区级的统计信息和每个分区的概要信息将能精确的生成全局级别的统计信息因此消除了需要扫描整个表的操作.当一个新的分区添加到表中,你仅仅需要对这个新的分区进行统计信息收集而已.表级别的统计信息将会使用新分区的概要信息和已经存的分区的概要信息来自动和精确的计算出来.

注意当增量统计信息被启用时分区统计信息不从子分区统计信息中进行聚合操作.

何时不收集统计信息
尽管oracle需要精确的统计信息来选择一个最优的执行计划,有些情况下收集统计信息是很困难的,很昂贵的或者是不能及时完成的所以要有一和睦替代的策略.

volatile表
一个volatile表是随着时间的变化数据量会发生很大改变的表.例如,一个订单队列表,这个表在一天开始的时候是空的,随着时间的推移订单将会填满这个表.当被处理的一个订单从表中删除时所以这一天结束时这个表会被再次清空.

如果你依赖自动统计信息收集job来维护象这样的表的统计信息那么这些表显示的统计信息总是空的因为收集job是在晚上.然而在当天工作期间这个表可能有成千上万行记录.

对于这样的表最好是在白天收集一组有代表性的统计信息并锁定这些信息.锁定这些统计信息将阻止自动统计信息收集job来覆盖它们.优化器在优化sql语句之前在编译sql语句时会使用动态抽样对表收集基本的统计信息.尽管通过动态抽样产生的统计信息质量不高或者不象使用dbms_stats包收集的统计信息那样完整但在大多数情况下已经够用了.

全局临时表
全局临时表在应用程序上下文中经常用来存储中间结果.一个全局临时表对于有合理权限的所有用户共享它的定义,但是数据只在各自的会话中可见.直到数据被插入表中之前是不分配物理存储的.一个全局临时表可能是transaction specific(提交时删除行(或session-specific(提交时保存行).对一个transaction specific的表收集统计信息将导致对这个表进行truncate操作.相反,可以对全局临时表收集统计信息.然而统计信息的收集将仅仅基于session-private数据内容进行收集但是这些统计信息将能被访问这个表的所有会话使用.

如果有一个全局临时表持续有行数据且每一个会话将有相同的数据量和相同的数值那么应该在一个会话中收集一组有代表性的统计信息并锁定它们防止其它会话将其覆盖.注意自动统计信息收集job是不会收集全局临时表的统计信息.

中间工作表
中间工作表是典型的一个ELT过程或者一个复杂事务的一部分.这些表只会写一次,读一次然后truncate或者delete.在这种情况下收集统计信息成本超过了它所带来的好处,因为统计信息只能用一次.相反动态抽样在这种情况下更有用.建议锁定中间工作表的统计信息来持久的阻止自动统计信息收集job来对它们收集统计信息.

收集其它类型的统计信息
因为现在只支持基于成本的优化器,数据库中所有的表需要有统计信息,包含所有的数据字典表(sys,system用户所拥有的表和内置在system和sysaux表空间中的表)和通过动态v$性能视图使用的x$表.

数据字典统计信息
数据字典表的统计信息是由自动统计信息收集job在晚维护窗口进行收集的.强烈建议你允许oracle自动统计信息收集job来维护数据字典统计信息即使在你关闭对主应用程序方案关闭自动统计信息收集的情况下.可以使用dbms_stats.set_global_prefs过程

将autostats_target从auto改成oracle

sys@JINGYONG> begin
  2  dbms_stats.set_global_prefs('AUTOSTATS_TARGET','ORACLE');
  3  end;
  4  /

PL/SQL 过程已成功完成。

固定对象统计信息
自动统计信息收集job不会收集固定对象的统计统计信息.当优化统计信息丢失时不象其它的数据库表对于sql语句中调用X$表是不能自动使用动态抽样的.如果它们的统计信息丢失优化器会使用预先定义的缺省统计信息.这些缺省的统计信息可能没有代表性且可能导致选择次优的执行计划,在系统中可能会导致严重的性能问题.如果是这个原因造成性能问题强烈建议你手动收集固定对象的统计信息.

可以使用dbms_stats.gather_fixed_objects_stats过程来收集固定对象的统计信息.因为在系统如果存在一个有代表性的工作负载收集x$这些固定对象的统计信息是很重要的.在大型系统中由于收集固定对象统计信息需要额外的资源所以对固定对象收集统计信息不总是可行.如果不能在负载高峰期间收集固定对象的统计信息那么应该在系统负载降低之后对三种关键类型的固定对象表收集统计信息:
structural data–比如controlfile contents
Session based data – 比如 v$session, v$access
Workload data -比如 v$sql, v$sql_plan
建议当主数据库或应用程序升级后,实现新的模块或者改变数据库的配置后重新收集固定对象统计信息.例如,如果增加SGA的大小包含缓冲区缓存和共享池信息的x$表会显著的发生改变,比如v$buffer_pool或v$shared_pool_advice视图使用的x$表.

系统统计信息
系统统计信息能让优化器通过使用执行这个语句相关的实际系统硬件信息,比如,cpu速度和IO性能,来在执行计划中对每一个步骤获得更精确的成本值.系统统计信息缺省情况下是启用的,它使用缺省值自动初始化,这些值对于大多数系统来说是有代表性的.

小结
为了让优化器准确的判断执行计划中的成本它必须有这个语句所访问的所对象的精确的统计信息和运行sql语句所在系统的系统统计信息.这里介绍了为什么必须要有统计信息,怎样使用统计信息和各种收集统计信息的方法.

通过使用自动统计信息收集job和其它收集技术相结合能让DBA维护一组精确的统计信息来确保优化器总是有必要的信息来选择最优的执行计划.一旦一个统计信息收集策略已经在使用时,要改变这个策略应该以一种受控的方式来利用一些关键特性比如pending统计信息来确保不会对程序性能造成影响.