首页 > 代码库 > [Oracle] 获取运行计划的各方法总结

[Oracle] 获取运行计划的各方法总结

总的结论:

一.获取运行计划的6种方法(具体步骤已经在每一个样例的开头凝视部分说明了):
1. explain plan for获取; 
2. set autotrace on 。 
3. statistics_level=all;
4. 通过dbms_xplan.display_cursor输入sql_id參数直接获取
5. 10046 trace跟踪
6. awrsqrpt.sql

二.适用场合分析

1.假设某SQL运行很长时间才会出结果。甚至慢到返回不了结果,这时候看运行计划就仅仅能用方法1,或者方法4调用现成的。
2.跟踪某条SQL最简单的方法是方法1,其次就是方法2。
3.假设想观察到某条SQL有多条运行计划的情况。仅仅能用方法4和方法6;
4.假设SQL中含有多函数,函数中套有SQL等多层递归调用,想准确分析。仅仅能用法5。
5.要想确保看到真实的运行计划,不能用方法1和方法2;

6.要想获取表被訪问的次数。仅仅能用法3;


环境构造

--研究Nested Loops Join訪问次数前准备工作

DROP TABLE t1 CASCADE CONSTRAINTS PURGE; 
DROP TABLE t2 CASCADE CONSTRAINTS PURGE; 
CREATE TABLE t1 (
     id NUMBER NOT NULL,
     n NUMBER,
     contents VARCHAR2(4000)
   )
   ; 
CREATE TABLE t2 (
     id NUMBER NOT NULL,
     t1_id NUMBER NOT NULL,
     n NUMBER,
     contents VARCHAR2(4000)
   )
   ; 
execute dbms_random.seed(0); 
INSERT INTO t1
     SELECT  rownum,  rownum, dbms_random.string(‘a‘, 50)
       FROM dual
     CONNECT BY level <= 1000
      ORDER BY dbms_random.random; 
INSERT INTO t2 SELECT rownum, rownum, rownum, dbms_random.string(‘b‘, 50) FROM dual CONNECT BY level <= 100000
    ORDER BY dbms_random.random; 
COMMIT; 
CREATE INDEX t1_n ON t1 (n);
CREATE INDEX t2_t1_id ON t2(t1_id);

以下我们将会用多种方法来查看例如以下语句的运行计划
SELECT  *
FROM t1, t2
WHERE t1.id = t2.t1_id
AND t1.n in(18,19);

方法1(explain plan for 的方式。

类似PLSQL DEVELOPE里的F5)

  步骤1:explain plan for "你的SQL"
  步骤2:select * from table(dbms_xplan.display()); 

set linesize 1000
set pagesize 2000
explain plan for
SELECT  *
FROM t1, t2
WHERE t1.id = t2.t1_id
AND t1.n in(18,19);
select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------
Plan hash value: 3532430033
-------------------------------------------------------------------------------------------
| Id  | Operation                      | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |          |     2 |  8138 |     6   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                  |          |       |       |            |          |
|   2 |   NESTED LOOPS                 |          |     2 |  8138 |     6   (0)| 00:00:01 |
|   3 |    INLIST ITERATOR             |          |       |       |            |          |
|   4 |     TABLE ACCESS BY INDEX ROWID| T1       |     2 |  4056 |     2   (0)| 00:00:01 |
|*  5 |      INDEX RANGE SCAN          | T1_N     |     1 |       |     1   (0)| 00:00:01 |
|*  6 |    INDEX RANGE SCAN            | T2_T1_ID |     1 |       |     1   (0)| 00:00:01 |
|   7 |   TABLE ACCESS BY INDEX ROWID  | T2       |     1 |  2041 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("T1"."N"=18 OR "T1"."N"=19)
   6 - access("T1"."ID"="T2"."T1_ID")
Note
-----
   - dynamic sampling used for this statement (level=2)

已选择24行。

长处:  1.无需真正运行。快捷方便
缺陷:  1.没有输出执行时的相关统计信息(产生多少逻辑读,多少次递归调用,多少次物理读的情况)。
        2.无法推断是处理了多少行;
        3.无法推断表被訪问了多少次。    
确实啊,这毕竟都没有真正执行又怎样得知真实执行产生的统计信息。

方法2(set autotrace on 方式)

  步骤1:set autotrace on 
  步骤2:在此处运行你的SQL就可以,兴许自然会有结果输出
  
另,有例如以下几种方式:
                     set autotrace on                 (得到执行计划,输出执行结果)
                     set autotrace traceonly          (得到执行计划,不输出执行结果)
                     set autotrace traceonly explain  (得到运行计划,不输出运行结果和统计信息部分,仅展现运行计划部分)
                     set autotrace traceonl statistics(不输出执行结果和执行计划部分,仅展现统计信息部分)
set autotrace on 
SELECT  *
FROM t1, t2
WHERE t1.id = t2.t1_id
AND t1.n in(18,19);

运行计划
----------------------------------------------------------
Plan hash value: 3532430033
-------------------------------------------------------------------------------------------
| Id  | Operation                      | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |          |     2 |  8138 |     6   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                  |          |       |       |            |          |
|   2 |   NESTED LOOPS                 |          |     2 |  8138 |     6   (0)| 00:00:01 |
|   3 |    INLIST ITERATOR             |          |       |       |            |          |
|   4 |     TABLE ACCESS BY INDEX ROWID| T1       |     2 |  4056 |     2   (0)| 00:00:01 |
|*  5 |      INDEX RANGE SCAN          | T1_N     |     1 |       |     1   (0)| 00:00:01 |
|*  6 |    INDEX RANGE SCAN            | T2_T1_ID |     1 |       |     1   (0)| 00:00:01 |
|   7 |   TABLE ACCESS BY INDEX ROWID  | T2       |     1 |  2041 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("T1"."N"=18 OR "T1"."N"=19)
   6 - access("T1"."ID"="T2"."T1_ID")
Note
-----
   - dynamic sampling used for this statement (level=2)
统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         12  consistent gets
          0  physical reads
          0  redo size
       1032  bytes sent via SQL*Net to client
        416  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

长处:1.能够输出执行时的相关统计信息(产生多少逻辑读,多少次递归调用,多少次物理读的情况);
        2.尽管必需要等语句运行完成后才干够输出运行计划。可是能够有traceonly开关来控制返回结果不打屏输出。


                  
缺陷:1.必需要等到语句真正运行完成后。才干够出结果;
        2.无法看到表被訪问了多少次。        

方法3(statistics level=all的方式)  

  步骤1:alter session set statistics_level=all ;
  步骤2:在此处运行你的SQL
  步骤3:select * from table(dbms_xplan.display_cursor(null,null,‘allstats last‘));
  
 另注:
 
  1. 假设你用 /*+ gather_plan_statistics */的方法,能够省略步骤1,直接步骤2,3。
  2. keyword解读(当中OMem、1Mem和User-Mem在兴许的课程中会陆续见到): 
    Starts为该sql运行的次数。
    E-Rows为运行计划估计的行数。
    A-Rows为实际返回的行数。A-Rows跟E-Rows做比較。就能够确定哪一步运行计划出了问题。
    A-Time为每一步实际运行的时间(HH:MM:SS.FF),依据这一行能够知道该sql耗时在了哪个地方。
    Buffers为每一步实际运行的逻辑读或一致性读。
    Reads为物理读。
    OMem:当前操作完毕全部内存工作区(Work Aera)操作所总共使用私有内存(PGA)中工作区的大小,
         这个数据是由优化器统计数据以及前一次运行的性能数据估算得出的
    1Mem:当工作区大小无法满足操作所需的大小时,须要将部分数据写入暂时磁盘空间中(假设仅须要写入一次就能够完毕操作,
         就称一次通过,One-Pass;否则为多次通过,Multi_Pass).该列数据为语句最后一次运行中,单次写磁盘所须要的内存
         大小,这个由优化器统计数据以及前一次运行的性能数据估算得出的
    User-Mem:语句最后一次运行中,当前操作所使用的内存工作区大小,括号中面为(发生磁盘交换的次数,1次即为One-Pass,
           大于1次则为Multi_Pass,假设没有使用磁盘,则显示OPTIMAL)
    OMem、1Mem为运行所需的内存评估值,0Mem为最优运行模式所需内存的评估值。1Mem为one-pass模式所需内存的评估值。
    0/1/M 为最优/one-pass/multipass运行的次数。Used-Mem耗的内存
  
set autotrace off          
alter session set statistics_level=all ;
SELECT  *
FROM t1, t2
WHERE t1.id = t2.t1_id
AND t1.n in(18,19);
select * from table(dbms_xplan.display_cursor(null,null,‘allstats last‘));

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------
SQL_ID  1a914ws3ggfsn, child number 0
-------------------------------------
SELECT  * FROM t1, t2 WHERE t1.id = t2.t1_id AND t1.n in(18,19)

Plan hash value: 3532430033
-----------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |          |      1 |        |      2 |00:00:00.01 |      12 |
|   1 |  NESTED LOOPS                  |          |      1 |        |      2 |00:00:00.01 |      12 |
|   2 |   NESTED LOOPS                 |          |      1 |      2 |      2 |00:00:00.01 |      10 |
|   3 |    INLIST ITERATOR             |          |      1 |        |      2 |00:00:00.01 |       5 |
|   4 |     TABLE ACCESS BY INDEX ROWID| T1       |      2 |      2 |      2 |00:00:00.01 |       5 |
|*  5 |      INDEX RANGE SCAN          | T1_N     |      2 |      1 |      2 |00:00:00.01 |       3 |
|*  6 |    INDEX RANGE SCAN            | T2_T1_ID |      2 |      1 |      2 |00:00:00.01 |       5 |
|   7 |   TABLE ACCESS BY INDEX ROWID  | T2       |      2 |      1 |      2 |00:00:00.01 |       2 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access(("T1"."N"=18 OR "T1"."N"=19))
   6 - access("T1"."ID"="T2"."T1_ID")
Note
-----
   - dynamic sampling used for this statement (level=2)

已选择29行。

长处:1.能够清晰的从STARTS得出表被訪问多少。
        2.能够清晰的从E-ROWS和A-ROWS中得到预測的行数和真实的行数。从而能够准确推断Oracle评估是否准确。


        3.尽管没有专门的输出执行时的相关统计信息,可是执行计划中的BUFFERS就是真实的逻辑读的多少
                 
缺陷:1.必需要等到语句真正运行完成后。才干够出结果。
        2.无法控制记录输屏打出。不像autotrace有 traceonly 能够控制不将结果打屏输出。
        3.看不出递归调用的次数。看不出物理读的多少(只是逻辑读才是重点)
    

方法4(知道sql_id后。直接带入的方式,简单,就步骤1)


步骤1: select  * from table(dbms_xplan.display_cursor(‘&sq_id‘)); (该方法是从共享池里得到)


注:
  1. 另一个方法,select  * from table(dbms_xplan.display_awr(‘&sq_id‘));(这是awr性能视图里获取到的)
  2. 假设有多运行计划,能够用类似方法查出
    select * from table(dbms_xplan.display_cursor(‘cyzznbykb509s‘,0));
    select * from table(dbms_xplan.display_cursor(‘cyzznbykb509s‘,1));

select * from table(dbms_xplan.display_cursor(‘1a914ws3ggfsn‘));
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------
SQL_ID  1a914ws3ggfsn, child number 0
-------------------------------------
SELECT  * FROM t1, t2 WHERE t1.id = t2.t1_id AND t1.n in(18,19)

Plan hash value: 3532430033
-------------------------------------------------------------------------------------------
| Id  | Operation                      | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |          |       |       |     6 (100)|          |
|   1 |  NESTED LOOPS                  |          |       |       |            |          |
|   2 |   NESTED LOOPS                 |          |     2 |  8138 |     6   (0)| 00:00:01 |
|   3 |    INLIST ITERATOR             |          |       |       |            |          |
|   4 |     TABLE ACCESS BY INDEX ROWID| T1       |     2 |  4056 |     2   (0)| 00:00:01 |
|*  5 |      INDEX RANGE SCAN          | T1_N     |     1 |       |     1   (0)| 00:00:01 |
|*  6 |    INDEX RANGE SCAN            | T2_T1_ID |     1 |       |     1   (0)| 00:00:01 |
|   7 |   TABLE ACCESS BY INDEX ROWID  | T2       |     1 |  2041 |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   5 - access(("T1"."N"=18 OR "T1"."N"=19))
   6 - access("T1"."ID"="T2"."T1_ID")

Note
-----
   - dynamic sampling used for this statement (level=2)

长处:1.知道sql_id马上可得到运行计划。和explain plan for 一样无需运行;
        2.能够得到真实的运行计划。(停。等等,啥真实的,刚才这几个套路中,还有假的运行计划的吗?)
        
                 
缺陷:  1.没有输出执行时的相关统计信息(产生多少逻辑读,多少次递归调用,多少次物理读的情况);
        2.无法推断是处理了多少行;  
        3.无法推断表被訪问了多少次。
        

方法5(10046TRACE)

  步骤1:alter session set events ‘10046 trace name context  forever,level 12‘; (开启跟踪)
  步骤2:运行你的语句
  步骤3:alter session set events ‘10046 trace name context off‘;   (关闭跟踪)
  步骤4:找到跟踪后产生的文件
  步骤5:tkprof  trc文件  目标文件  sys=no sort=prsela,exeela,fchela  (格式化命令)     

set autotace off
alter session set statistics_level=typical;     
alter session set events ‘10046 trace name context  forever,level 12‘;

SELECT  *
FROM t1, t2
WHERE t1.id = t2.t1_id
AND t1.n in(18,19);   
   
alter session set events ‘10046 trace name context off‘;   
select d.value
|| ‘/‘
|| LOWER (RTRIM(i.INSTANCE, CHR(0)))
|| ‘_ora_‘
|| p.spid
|| ‘.trc‘ trace_file_name
from (select p.spid
      from v$mystat m,v$session s, v$process p
      where  m.statistic#=1 and s.sid=m.sid and p.addr=s.paddr) p,
      (select t.INSTANCE
       FROM v$thread t,v$parameter v
       WHERE v.name=‘thread‘
       AND(v.VALUE=http://www.mamicode.com/0 OR t.thread#=to_number(v.value))) i,>
长处:1.能够看出SQL语句相应的等待事件
        2.假设SQL语句中有函数调用,SQL中有SQL,将会都被列出,无处遁形。
        3.能够方便的看出处理的行数,产生的物理逻辑读。
        4.能够方便的看出解析时间和运行时间。
        5.能够跟踪整个程序包
                                 
缺陷: 1.步骤繁琐。比較麻烦
        2.无法推断表被訪问了多少次。
        3.运行计划中的谓词部分不能清晰的展现出来。 


方法6. awrsqrpt.sql   

  步骤1:@?/rdbms/admin/awrsqrpt.sql
  步骤2:选择你要的断点(begin snap 和end snap)
  步骤3:输入你的sql_id     


[Oracle] 获取运行计划的各方法总结