通过dbms_xplan.display_cursor识别低效的执行计划

dbms_xplan.display_cursor定义:

function display_cursor(sql_id           varchar2 default  null,
                                      cursor_child_no  integer  default  0,
                                      format          varchar2 default  'TYPICAL')
  return dbms_xplan_type_table
  pipelined;

 

识别问题sql我们可以通过以下几种方式获得:
1. AWR
2. ASH
3. 根据当时占用CPU高的进程查找对应的sql

问题sql找到了,接着我们要看sql的执行计划,但问题是如果快速找到执行计划中哪个地方出了问题呢?

如果我们能得到sql执行时每一步实际返回的行数,就可以跟执行计划预计返回的行数做比较,两者数据相差不大,可以认为执行计划没有问题;反之,两者差距悬殊,就表示执行计划出了问题。

从10g开始oracle提供了dbms_xplan包的display_cursor函数,来同时显示执行计划预期返回的行数和实际返回的行数,于是我们可以利用这个包来快速找到执行计划中哪个地方出了问题,然后对症采取办法。

dbms_xplan包的display_cursor函数是从libary cache中获取执行计划,所以要想访问该函数,必须先授予权限:grant select any dictionary to scott;

使用dbms_xplan.display_cursor函数的步骤是:

1. 设置初始化参数statistics_level为ALL,如下:
SQL> alter session set statistics_level='ALL';
statistics_level控制数据库收集统计信息的级别,有三个值:
BASIC :收集基本的统计信息
TYPICAL:收集大部分统计信息(数据库的默认设置)
ALL:收集全部统计信息

2. 执行问题sql,如:
SQL> select ename,sal from emp,dept where emp.deptno=dept.deptno and dept.loc='CHICAGO';

3. 使用dbms_xplan.display_cursor包,查看带实际返回行的执行计划:
set lines 300
set pages 9000
SQL> select * from table(dbms_xplan.display_cursor(null,0,'allstats last'));

dbms_xplan.display_cursor函数的定义是:
  -- display from V$SQL_PLAN (or V$SQL_PLAN_STATISTICS_ALL)
  function display_cursor(sql_id           varchar2 default  null,
                          cursor_child_no  integer  default  0,
                          format           varchar2 default  'TYPICAL')
  return dbms_xplan_type_table
  pipelined; 

其中参数sql_id为父游标,如果为null,表示显示该会话之前的sql执行计划。cursor_child_no为子游标的序号,默认为0,如果设定为NULL,则所有该父游标下所有的子游标的执行计划都将返回。
参数format指定要显示哪些信息,常用的有:iostats(i/o信息显示)、allstats(i/o信息显示+pga信息)、advanced(显示所有统计信息)、iostats last或allstats last(只显示最后一次执行的统计信息)。默认值TYPICAL只能显示一个普通的执行计划,不能显示出实际返回的行。

第3步的输出为:
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  cuq0vs99sctnm, child number 0
-------------------------------------
select ename,sal from emp,dept where emp.deptno=dept.deptno and
dept.loc='CHICAGO'

Plan hash value: 844388907

-----------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |      1 |        |      6 |00:00:00.01 |      11 |       |       |          |
|   1 |  MERGE JOIN                  |         |      1 |      4 |      6 |00:00:00.01 |      11 |       |       |          |
|*  2 |   TABLE ACCESS BY INDEX ROWID| DEPT    |      1 |      1 |      1 |00:00:00.01 |       4 |       |       |          |
|   3 |    INDEX FULL SCAN           | PK_DEPT |      1 |      4 |      4 |00:00:00.01 |       2 |       |       |          |
|*  4 |   SORT JOIN                  |         |      1 |     14 |      6 |00:00:00.01 |       7 |  2048 |  2048 | 2048  (0)|
|   5 |    TABLE ACCESS FULL         | EMP     |      1 |     14 |     14 |00:00:00.01 |       7 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------

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

   2 - filter("DEPT"."LOC"='CHICAGO')
   4 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")
       filter("EMP"."DEPTNO"="DEPT"."DEPTNO")

25 rows selected.

解读以上输出:
Starts为该sql执行的次数。
E-Rows为执行计划预计的行数。
A-Rows为实际返回的行数。A-Rows跟E-Rows做比较,就可以确定哪一步执行计划出了问题。
A-Time为每一步实际执行的时间(HH:MM:SS.FF),根据这一行可以知道该sql耗时在了哪个地方。
Buffers为每一步实际执行的逻辑读或一致性读。
Reads为物理读。
OMem、1Mem为执行所需的内存评估值,0Mem为最优执行模式所需内存的评估值,1Mem为one-pass模式所需内存的评估值。
0/1/M 为最优/one-pass/multipass执行的次数。

查找低效执行计划:
1.比较A-Rows/Starts跟E-Rows,如果两值差别悬殊,则该行是低效执行计划。
2.查看Buffers/A-rows的比率,即返回一行平均消耗多少逻辑读
Buffers/A-rows<5 表示访问路径不错
Buffers/A-rows between 10 and 15,表示访问路径可以接受
Buffers/A-rows>15or20,表示路径不好,该行是低效执行计划,可以优化

另一种使用dbms_xplan.display_cursor函数的步骤是:
1. 在问题sql中加入提示:gather_plan_statistics
SQL> select /*+ gather_plan_statistics */ ename,sal from emp,dept where emp.deptno=dept.deptno and dept.loc='CHICAGO';

2. 使用dbms_xplan.display_cursor包,查看带实际返回行的执行计划:
set lines 300
set pages 9000
SQL> select * from table(dbms_xplan.display_cursor(null,0,'allstats last'));

 

根据dbms_xplan.display_cursor函数可以很轻易地找到执行计划的哪个地方出了问题,接着我们就来分析CBO做出错误执行计划的原因。
如果出问题的那一步对应的是个索引,我们可以根据以下sql来判断是否统计信息出了问题:

0. 检查是否是索引碎片造成的
exec p_show_space('索引名', '索引属主', 'index')

1. 查看该索引的统计信息:
select index_name,num_rows,distinct_keys,num_rows/distinct_keys as avg_rows_per_key,last_analyzed  from user_indexes where index_name='索引名';  如下:
SQL> select index_name,num_rows,distinct_keys,num_rows/distinct_keys avg_rows_per_key,last_analyzed from user_indexes where index_name='PK_DEPT';

2. 查看实际的数据
select count(*) num_rows,count(nullif(col1,列值)) distinct_keys, count(distinct 列名) avg_rows_per_key from 表名;  如下:
SQL> select count(*) num_rows,count(nullif(loc,'CHICAGO'))  distinct_keys,count(distinct 'CHICAGO') avg_rows_per_key from dept;

如果上面两步前两列数据差别悬殊,则说明统计信息不准确,需要重新收集统计信息;如果前两列数据相差不大,第三列数据是number,且相差很大,则可能索引所在列的直方图出了问题。

3. 查看索引列是否做了直方图统计
alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss';
select column_name,histogram,num_buckets,last_analyzed from user_tab_cols where table_name='EMP' and column_name in ('EMPNO','ENAME');
COLUMN_NAME                    HISTOGRAM       NUM_BUCKETS LAST_ANALYZED
------------------------------ --------------- ----------- -------------------
EMPNO                          NONE                                    1   2012-10-23 22:48:28
ENAME                          HEIGHT BALANCED          16 2012-10-23 22:48:28
histogram列为NONE则表示未收集直方图。

 

直方图收集:
method_opt参数取值: size 1不搜集,size 2~255会搜集,size auto自动判断,size skewonly只要数据倾斜则收集。method_opt参数默认值为for all columns size auto,oracle自己决定采样比率。发现此默认值有时收集的直方图很有限,必要时可对所有索引列收集直方图 (不建议对所有列收集直方图,因为这样会导致存储直方图信息的表sys.histgrm$过大)
SQL> exec dbms_stats.gather_table_stats(user,'DEPT',method_opt =>'for all indexed columns',cascade=>true)

 

以下为个人牵强补充:
如果出问题的那一步对应的是个表,我们可以根据以下sql来判断是否统计信息出了问题:
1. 查看该表的统计信息及碎片率:
SQL> select num_rows from user_tables where table_name='EMP';
select num_rows,avg_row_len*num_rows/1024/blocks*8*100 碎片率 from user_tables where table_name='T';  --这种计算碎片率的方式仅适用于默认参数storage(initial 64K)建立的表
或用exec p_show_space('表名', '表属主', 'table')

2. 查看实际的数据
SQL> select count(*) num_rows from emp;
收集表的统计信息:exec dbms_stats.gather_table_stats(user,'表名',cascade=>true)   --cascade=>true表示同时收集索引

http://pandarabbit.blog.163.com/blog/static/209284144201292910217427/

http://blog.csdn.net/dbanote/article/details/24516037

时间: 2024-08-31 13:21:36

通过dbms_xplan.display_cursor识别低效的执行计划的相关文章

执行计划中各字段各模块描述

      在SQL语句的执行计划中,包含很多字段项和很多模块,其不同字段代表了不同的含义且在不同的情形下某些字段.模块显示或不显示,下面的描述给出了执行计划中各字段的含义以及各模块的描述.        有关执行计划中各字段模块的描述请参考: 执行计划中各字段各模块描述        有关由SQL语句来获取执行计划请参考:     使用 EXPLAIN PLAN 获取SQL语句执行计划        有关使用autotrace来获取执行计划请参考:启用 AUTOTRACE 功能       有

一次有意思的错选执行计划问题定位(涉及SYS_OP_C2)

这两天和广分的兄弟看了一个问题,比较有意思,过程也比较曲折... 问题现象: 1. 11g的库,话说有一个应用程序新上线,应用中使用了绑定变量的方式执行一条简单的SQL,例如select a from b where c = :x,c列是该表复合主键的前导列,表定义是varchar2类型,从spotlight监控看这条SQL的执行计划是全表扫描,一次执行要1个小时,这张表是运行很久的引用分区表,数据量是亿级,测试的时候正常,但很显然测试的数据量可能和生产非常不一致,导致没察觉. 2. 在sqlp

Oracle中使用DBMS_XPLAN处理执行计划详解_oracle

DBMS_XPLAN是Oracle提供的一个用于查看SQL计划,包括执行计划和解释计划的包:在以前查看SQL执行计划的时候,我都是使用set autotrace命令,不过现在看来,DBMS_XPLAN包给出了更加简化的获取和显示计划的方式. 这5个函数分别对应不同的显示计划的方式,DBMS_XPLAN包不仅可以获取解释计划,它还可以用来输出存储在AWR,SQL调试集,缓存的SQL游标,以及SQL基线中的语句计划,实现如上的功能,通常会用到一下5个方法: 1.DISPLAY 2.DISPLAY_A

dbms_xplan.display_awr方式获取执行计划的实验和之前的误导

<查看Oracle执行计划的几种常用方法-系列1>(http://blog.csdn.net/bisal/article/details/38919181)这篇博文中曾提到一个隐藏问题: "隐藏问题2: 实验这部分内容发现使用select * from table(dbms_xplan.display_awr('sql_id'));并没有结果,@黄玮老师说有可能是AWR收集的是top的SQL,有可能测试用的SQL不是most intensive SQL,但我是用alter syste

Oracle中基于hint的3种执行计划控制方法详细介绍_oracle

hint(提示)无疑是最基本的控制执行计划的方式了:通过在SQL语句中直接嵌入优化器指令,进而使优化器在语句执行时强制的选择hint指定的执行路径,这种使用方式最大的好处便是方便和快捷,定制度也很高,通常在对某些SQL语句执行计划进行微调的时候我会首选这种方式,不过尽管如此,hint在使用中仍然有很多不可忽视的问题: 使用hint过程中有一些值得注意的细则,首先便是要准确的识别对应的查询块,如果需要使用注释也可以hint中声明:对于使用别名的对象一律使用别名来引用,并且诸如"用户名.对象&quo

Oracle 全表扫描及其执行计划(full table scan)

    全表扫描是Oracle访问数据库表是较为常见的访问方式之一.很多朋友一看到SQL语句执行计划中的全表扫描,就要考虑对其进行修理一番.全表扫描的存在,的确存在可能优化的余地.但事实上很多时候全表扫描也并非是最低效的,完全要看不同的情形与场合,任一方式都是有利有弊的,也就是具体情况要具体分析.本文描述了什么是全表扫描以及何时发生全表扫描,何时全表扫描才低效.  本文涉及到的相关链接:     高水位线和全表扫描      启用 AUTOTRACE 功能     Oracle 测试常用表BIG

如何获取SQL执行计划

*********************************************************** ----1:获取"刚刚"的执行计划display_cursor *********************************************************** Explain plan命令在Oracle中,可以对后面的SQL语句进行直接的解析,将执行计划保存在一个plan_table的中间表中.之后通过dbms_xplan包的方法进行获取. s

Oracle中获取执行计划的几种方法

1. 预估执行计划 - Explain Plan Explain plan以SQL语句作为输入,得到这条 SQL语句的执行计划,并将执行计划输出存储到计划表中. 首先,在你要执行的SQL语 句前加explain plan for,此时将生成的执行计划存储到计划表中,语句如下: explain plan for SQL语句 然后,在计划表中查询刚刚生成的执行计划,语 句如下: select * from table(dbms_xplan.display); 注意:Explain plan 只生成执

使用Oracle脚本查看执行计划

声明:脚本来自<Pro Oracle SQL>一书,pln.sql 下面只是一个查看执行计划一种方法,就是通过加上备注表示唯一SQL语句: [oracle@maa3 ~]$ cat pln.sql SELECT xplan.* FROM     (   select max(sql_id) keep  (dense_rank last order by last_active_time) sql_id , max(child_number) keep  (dense_rank last ord