oracle读懂执行计划之得到sql的执行计划

一.获取执行计划的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

--环境构造 
--研究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);

下面我们针对每一个执行计划来做试验

方法1(explain plan for 的方式。类似PLSQL DEVELOPE里的F5)

SQL> set linesize 1000
SQL> set pagesize 2000
SQL> explain plan for
  2 SELECT *
  3 FROM t1, t2
  4 WHERE t1.id = t2.t1_id
  5 AND t1.n in(18,19);
已解释。
SQL> 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  | 11 (0)| 00:37:20 | 
| 1 | NESTED LOOPS               |          |       |       |       |          | 
| 2 | NESTED LOOPS               |          | 2     | 8138  | 11 (0)| 00:37:20 | 
| 3 | INLIST ITERATOR            |          |       |       |       |          | 
| 4 | TABLE ACCESS BY INDEX ROWID| T1       | 2     | 4056  | 5 (0) | 00:16:59 | 
|*5 | INDEX RANGE SCAN           | T1_N     | 4     |       | 2 (0) | 00:06:48 | 
|*6 | INDEX RANGE SCAN           | T2_T1_ID | 1     |       | 1 (0) | 00:03:24 | 
| 7 | TABLE ACCESS BY INDEX ROWID| T2       | 1     | 2041  | 3 (0) | 00:10:11 | 
-------------------------------------------------------------------------------- ----------- 
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 traceonly
   set autotrace on (得到执行计划,输出运行结果) 
  set autotrace traceonly (得到执行计划,不输出运行结果)
  set autotrace traceonly explain (得到执行计划,不输出运行结果和统计信息部分,仅展现执行计划部分)
  set autotrace traceonl statistics(不输出运行结果和执行计划部分,仅展现统计信息部分)

SQL> set autotrace traceonly
SQL> SELECT *
  2 FROM t1, t2
  3 WHERE t1.id = t2.t1_id
  4 AND t1.n in(18,19);
执行计划
----------------------------------------------------------
Plan hash value: 3532430033
--------------------------------------------------------------------------------  ----------- 
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|   Time | 
--------------------------------------------------------------------------------  ----------- 
| 0 | SELECT STATEMENT | | 2 | 8138 | 11 (0)|   00:37:20 | 
| 1 | NESTED LOOPS | | | | |            | 
| 2 | NESTED LOOPS | | 2 | 8138 | 11 (0)|   00:37:20 | 
| 3 | INLIST ITERATOR | | | | |            | 
| 4 | TABLE ACCESS BY INDEX ROWID| T1 | 2 | 4056 | 5 (0)|   00:16:59 | 
|* 5 | INDEX RANGE SCAN | T1_N | 4 | | 2 (0)|   00:06:48 | 
|* 6 | INDEX RANGE SCAN | T2_T1_ID | 1 | | 1 (0)|   00:03:24 | 
| 7 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 2041 | 3 (0)|   00:10:11 | 
--------------------------------------------------------------------------------  ----------- 
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)
统计信息
----------------------------------------------------------
          7 recursive calls
          0 db block gets
         95 consistent gets
          8 physical reads
          0 redo size
       1200 bytes sent via SQL*Net to client
        519 bytes received via SQL*Net from client
          2 SQL*Net roundtrips to/from client
          0 sorts (memory)
          0 sorts (disk)
          2 rows processed
SQL> set autotrace off


优点: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. 关键字解读(其中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耗的内存
         
SQL> alter session set statistics_level=all ;
会话已更改。
SQL> SELECT *
  2 FROM t1, t2
  3 WHERE t1.id = t2.t1_id
  4 AND t1.n in(18,19);

SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  1a914ws3ggfsn , child number 1
-------------------------------------
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 | 14      | 
| 1  | NESTED LOOPS               |          | 1      |        | 2      |00  :00:00.01 | 14      | 
| 2  | NESTED LOOPS               |          | 1      | 2      | 2      |00  :00:00.01 | 12      | 
| 3  | INLIST ITERATOR            |          | 1      |        | 2      |00  :00:00.01 | 7       | 
| 4  | TABLE ACCESS BY INDEX ROWID| T1       | 2      | 2      | 2      |00  :00:00.01 | 7       | 
|* 5 | INDEX RANGE SCAN           | T1_N     | 2      | 4      | 2      |00  :00:00.01 | 5       | 
|* 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)

优点:1.可以清晰的从STARTS得出表被访问多少。
     2.可以清晰的从E-ROWS和A-ROWS中得到预测的行数和真实的行数,从而可以准确判断Oracle评估是否准确。
     3.虽然没有专门的输出运行时的相关统计信息,但是执行计划中的BUFFERS就是真实的逻辑读的多少
                 
缺陷:1.必须要等到语句真正执行完毕后,才可以出结果。
     2.无法控制记录输屏打出,不像autotrace有 traceonly 可以控制不将结果打屏输出。
     3.看不出递归调用的次数,看不出物理读的多少(不过逻辑读才是重点)

方法4(知道sql_id后,直接带入的方式)

步骤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));

SQL> 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 | | | | 11 (100)|            | 
| 1 | NESTED LOOPS | | | | |            | 
| 2 | NESTED LOOPS | | 2 | 8138 | 11 (0)|   00:37:20 | 
| 3 | INLIST ITERATOR | | | | |            | 
| 4 | TABLE ACCESS BY INDEX ROWID| T1 | 2 | 4056 | 5 (0)|  00:16:59 | 
|* 5 | INDEX RANGE SCAN | T1_N | 4 | | 2 (0)|  00:06:48 | 
|* 6 | INDEX RANGE SCAN | T2_T1_ID | 1 | | 1 (0)|  00:03:24 | 
| 7 | TABLE ACCESS BY INDEX ROWID | T2 | 1 | 2041 | 3 (0)|  00:10:11 | 
--------------------------------------------------------------------------------  ----------- 
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.可以得到真实的执行计划。( 和explain plan for不同的是,此时的执行计划为oracle实际执行过的,并非预估值
       
                 
缺陷 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 (格式化命令) 

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=0 OR t.thread#=to_number(v.value))) i,
       (select value
       from v$parameter
       where name='user_dump_dest') d;  -- 找到相关的trace语句 10g的方法



SQL> alter session set statistics_level=typical;
会话已更改。
SQL> alter session set events '10046 trace name context forever,level 12';
会话已更改。
SQL> SELECT *
  2 FROM t1, t2
  3 WHERE t1.id = t2.t1_id
  4 AND t1.n in(18,19);

SQL> select d.value
  2 || '/'
  3 || LOWER (RTRIM(i.INSTANCE, CHR(0)))
  4 || '_ora_'
  5 || p.spid
  6 || '.trc' trace_file_name
  7 from (select p.spid
  8 from v$mystat m,v$session s, v$process p
  9 where m.statistic#=1 and s.sid=m.sid and p.addr=s.paddr) p,
 10 (select t.INSTANCE
 11 FROM v$thread t,v$parameter v
 12 WHERE v.name='thread'
 13 AND(v.VALUE=0 OR t.thread#=to_number(v.value))) i,
 14 (select value
 15 from v$parameter
 16 where name='user_dump_dest') d;
TRACE_FILE_NAME
--------------------------------------------------------------------------------
d:\app\zhoukai\diag\rdbms\orcl\orcl\trace/orcl_ora_9336.trc



C:\Users\zhoukai>tkprof d:\app\zhoukai\diag\rdbms\orcl\orcl\trace/orcl_ora_9336. trc d:\trace.txt sys=no sort=prsela,exeela,fchela 
TKPROF: Release 11.2.0.1.0 - Development on 星期日 4月 27 21:41:09 2014
Copyright (c) 1982, 2009, Oracle and/or its affiliates. All rights reserved.


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.03 0.04 0 81 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.00 0.00 0 14 0 2
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.03 0.04 0 95 0 2
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84
Rows Row Source Operation
------- ---------------------------------------------------
      2 NESTED LOOPS (cr=14 pr=0 pw=0 time=0 us)
      2 NESTED LOOPS (cr=12 pr=0 pw=0 time=25 us cost=11 size=8138 card=2)
      2 INLIST ITERATOR (cr=7 pr=0 pw=0 time=17 us)
      2 TABLE ACCESS BY INDEX ROWID T1 (cr=7 pr=0 pw=0 time=0 us cost=5 size=4056 card=2)
      2 INDEX RANGE SCAN T1_N (cr=5 pr=0 pw=0 time=0 us cost=2 size=0 card=4)(object id 85681)
      2 INDEX RANGE SCAN T2_T1_ID (cr=5 pr=0 pw=0 time=0 us cost=1 size=0 card=1)(object id 85682)
      2 TABLE ACCESS BY INDEX ROWID T2 (cr=2 pr=0 pw=0 time=0 us cost=3 size=2041 card=1)
Elapsed times include waiting on following events:
  Event waited on Times Max. Wait Total Waited
  ---------------------------------------- Waited ---------- ------------
  SQL*Net message to client 2 0.00 0.00
  SQL*Net message from client 2 10.74 10.74


   优点:1.可以看出SQL语句对应的等待事件
        2.如果SQL语句中有函数调用,SQL中有SQL,将会都被列出,无处遁形。
        3.可以方便的看出处理的行数,产生的物理逻辑读。
        4.可以方便的看出解析时间和执行时间。
        5.可以跟踪整个程序包
       
       
                 
   缺陷: 1.步骤繁琐,比较麻烦
        2.无法判断表被访问了多少次。
        3.执行计划中的谓词部分不能清晰的展现出来。

awr就不做测试了

你可能感兴趣的:(oracle学习,oracle,执行计划)