oracle学习笔记6: SQL执行计划

解释计划

语句explain plan用来显示优化器为sql语句选择的执行计划。

oracle学习笔记6: SQL执行计划_第1张图片
SQL执行计划
set autotrace off;
set autotrace traceonly;
explain plan for 
select e.last_name||','||e.first_name as full_name,
e.phone_number,e.email,e.department_id,d.department_name,c.country_name,l.city,l.state_province,
r.region_name
from hr.employees e,hr.departments d,hr.countries c,
hr.locations l,hr.regions r
where e.department_id=d.department_id
and d.location_id=l.location_id
and l.country_id=c.country_id
and c.region_id=r.region_id;
select * from table(dbms_xplan.display);
select * from table(dbms_xplan.display(format=>'BASIC +COST +PREDICATE'));

执行结果

SQL> 
Cannot SET AUTOTRACE
Cannot SET AUTOTRACE
Explained
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2498281325
--------------------------------------------------------------------------------
| Id  | Operation                       | Name             | Rows  | Bytes | Cos
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |                  |   106 | 11766 |
|*  1 |  HASH JOIN                      |                  |   106 | 11766 |
|*  2 |   HASH JOIN                     |                  |    27 |  1890 |
|   3 |    NESTED LOOPS                 |                  |    27 |  1512 |
|   4 |     MERGE JOIN                  |                  |    27 |  1134 |
|   5 |      TABLE ACCESS BY INDEX ROWID| DEPARTMENTS      |    27 |   513 |
|   6 |       INDEX FULL SCAN           | DEPT_LOCATION_IX |    27 |       |
|*  7 |      SORT JOIN                  |                  |    23 |   529 |
|   8 |       TABLE ACCESS FULL         | LOCATIONS        |    23 |   529 |
|*  9 |     INDEX UNIQUE SCAN           | COUNTRY_C_ID_PK  |     1 |    14 |
|  10 |    TABLE ACCESS FULL            | REGIONS          |     4 |    56 |
|  11 |   TABLE ACCESS FULL             | EMPLOYEES        |   107 |  4387 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
---------------------------------------------------
   1 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   2 - access("C"."REGION_ID"="R"."REGION_ID")
   7 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
       filter("D"."LOCATION_ID"="L"."LOCATION_ID")
   9 - access("L"."COUNTRY_ID"="C"."COUNTRY_ID")
27 rows selected
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2498281325
-------------------------------------------------------------------------
| Id  | Operation                       | Name             | Cost (%CPU)|
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |                  |    13  (16)|
|*  1 |  HASH JOIN                      |                  |    13  (16)|
|*  2 |   HASH JOIN                     |                  |    10  (20)|
|   3 |    NESTED LOOPS                 |                  |     6  (17)|
|   4 |     MERGE JOIN                  |                  |     6  (17)|
|   5 |      TABLE ACCESS BY INDEX ROWID| DEPARTMENTS      |     2   (0)|
|   6 |       INDEX FULL SCAN           | DEPT_LOCATION_IX |     1   (0)|
|*  7 |      SORT JOIN                  |                  |     4  (25)|
|   8 |       TABLE ACCESS FULL         | LOCATIONS        |     3   (0)|
|*  9 |     INDEX UNIQUE SCAN           | COUNTRY_C_ID_PK  |     0   (0)|
|  10 |    TABLE ACCESS FULL            | REGIONS          |     3   (0)|
|  11 |   TABLE ACCESS FULL             | EMPLOYEES        |     3   (0)|
-------------------------------------------------------------------------
Predicate Information (identified by operation id):
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
---------------------------------------------------
   1 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   2 - access("C"."REGION_ID"="R"."REGION_ID")
   7 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
       filter("D"."LOCATION_ID"="L"."LOCATION_ID")
   9 - access("L"."COUNTRY_ID"="C"."COUNTRY_ID")

这儿使用了explain plan命令和sql*plus autotrace命令来生成解释计划输出。使用autotrace可以自动生成计划,使得你所要做的事件就是打开autotrace并执行一个查询,当使用这个方法生成查询,并不实际执行查询,只产生预期的执行计划。

plan_table

解释计划输出中所看到的信息是由explain plan命令生成并默认存储在表plan_table中的。autotrace命令从所提供的dbms_xplan包中调用display函数来自动生成输出。当使用explan plan命令时你必须手工执行查询。


SQL> desc plan_table;
 名称                                      是否为空? 类型
 ----------------------------------------- -------- ----------------------------
 STATEMENT_ID                                       VARCHAR2(30)
 PLAN_ID                                            NUMBER
 TIMESTAMP                                          DATE
 REMARKS                                            VARCHAR2(4000)
 OPERATION                                          VARCHAR2(30)
 OPTIONS                                            VARCHAR2(255)
 OBJECT_NODE                                        VARCHAR2(128)
 OBJECT_OWNER                                       VARCHAR2(30)
 OBJECT_NAME                                        VARCHAR2(30)
 OBJECT_ALIAS                                       VARCHAR2(65)
 OBJECT_INSTANCE                                    NUMBER(38)
 OBJECT_TYPE                                        VARCHAR2(30)
 OPTIMIZER                                          VARCHAR2(255)
 SEARCH_COLUMNS                                     NUMBER
 ID                                                 NUMBER(38)
 PARENT_ID                                          NUMBER(38)
 DEPTH                                              NUMBER(38)
 POSITION                                           NUMBER(38)
 COST                                               NUMBER(38)
 CARDINALITY                                        NUMBER(38)
 BYTES                                              NUMBER(38)
 OTHER_TAG                                          VARCHAR2(255)
 PARTITION_START                                    VARCHAR2(255)
 PARTITION_STOP                                     VARCHAR2(255)
 PARTITION_ID                                       NUMBER(38)
 OTHER                                              LONG
 OTHER_XML                                          CLOB
 DISTRIBUTION                                       VARCHAR2(30)
 CPU_COST                                           NUMBER(38)
 IO_COST                                            NUMBER(38)
 TEMP_SPACE                                         NUMBER(38)
 ACCESS_PREDICATES                                  VARCHAR2(4000)
 FILTER_PREDICATES                                  VARCHAR2(4000)
 PROJECTION                                         VARCHAR2(4000)
 TIME                                               NUMBER(38)
 QBLOCK_NAME                                        VARCHAR2(30)

dbms_xplan.display一个非常好的特性是它可以基于每个特定的SQL语句生成的执行计划而自动显示适当的列。如果计划中使用了分区的运算,在输出中就会包含partition_start,partition_stop以及partition_id这些列。

描述
ID 为每个步骤分配的唯一的编号
OPERATION 这一步骤所进行的内部运算
OPTIONS 运算列的附加说明(附于OPERATION)
OBJECT_NAME 表或索引的名称
COST 由优化器确定的运算所需要的成本值
ACCESS_PREDICATES 用来在访问结构(一般为索引)中确定数据行所在位置的条件
FILTER_PREDICATES 用来在数据行被访问后进行筛选的条件

分解计划

总的来说,只有一个子运算可以分为以下3类

  • 加工运算 从子运算接收一一个数据库行集并经过加工以后传递给其父运算
  • 传递运算 只是起传递的作用而不对来自子运算的数据做任何修改或加工。它们基本上是用来确定某个运算的特性。VIEW运算就是传递运算的一个很好的例子。
  • 迭代运算 表示子运算要多次执行。通常会在这类运算上看到iterator,inlist或all等字眼。

导致解释计划未达到目的的原因
解释计划输出可能与语句实际执行时的使用计划可能不一致。

  • 解释计划是基于你使用它的时候的环境来生成的
  • 解释计划不考虑绑定变量的数据类型
  • 解释计划不“窥视”绑定变量的值
  • explain plan只显示原始计划而不显示最终的计划。
--解释计划与绑定变量数据类型
--创建一个测试表,主键设置为string数据类型
drop table regions2;
create table regions2(
       region_id varchar2(10) primary key,
       region_name varchar2(25)
);
--插入记录到这个表中
insert into regions2
select * from hr.regions;

--创建一个变量并设置值
variable regid number;
exec :regid := 1;
--得到这个查询的解释计划
explain plan for 
select /* DataTypeTest */ * 
from regions2
where region_id =: regid;

select * from table(dbms_xplan.display(format=>'BASIC +COST +PREDICATE'));

select * from table(dbms_xplan.display_cursor(null,null,format=>'BASIC +COST +PREDICATE'));

解释计划不考虑绑定变量的数据类型并假设所有的绑定变量都是字符串类型的方式。对于解释计划来说,数据类型被认为都是一样的。然而,当语句真正执行时所准备的执行计划却要考虑数据类型。
谓语必须严格匹配索引定义,否则将不会使用索引。

阅读计划

有3种途径有助于阅读和理解所有计划:

  1. 学会识别和分割父子组
  2. 掌握计划中运算执行的顺序
  3. 学会以叙述的形式阅读计划

解释计划例子

set autotrace off;
set autotrace traceonly;
explain plan for 
select e.last_name||','||e.first_name as full_name,
e.phone_number,e.email,e.department_id,d.department_name,c.country_name,l.city,l.state_province,
r.region_name
from hr.employees e,hr.departments d,hr.countries c,
hr.locations l,hr.regions r
where e.department_id=d.department_id
and d.location_id=l.location_id
and l.country_id=c.country_id
and c.region_id=r.region_id;
select * from table(dbms_xplan.display(format=>'BASIC +COST +PREDICATE'));

Explained
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2498281325
-------------------------------------------------------------------------
| Id  | Operation                       | Name             | Cost (%CPU)|
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |                  |    13  (16)|
|*  1 |  HASH JOIN                      |                  |    13  (16)|
|*  2 |   HASH JOIN                     |                  |    10  (20)|
|   3 |    NESTED LOOPS                 |                  |     6  (17)|
|   4 |     MERGE JOIN                  |                  |     6  (17)|
|   5 |      TABLE ACCESS BY INDEX ROWID| DEPARTMENTS      |     2   (0)|
|   6 |       INDEX FULL SCAN           | DEPT_LOCATION_IX |     1   (0)|
|*  7 |      SORT JOIN                  |                  |     4  (25)|
|   8 |       TABLE ACCESS FULL         | LOCATIONS        |     3   (0)|
|*  9 |     INDEX UNIQUE SCAN           | COUNTRY_C_ID_PK  |     0   (0)|
|  10 |    TABLE ACCESS FULL            | REGIONS          |     3   (0)|
|  11 |   TABLE ACCESS FULL             | EMPLOYEES        |     3   (0)|
-------------------------------------------------------------------------
Predicate Information (identified by operation id):
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
---------------------------------------------------
   1 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
   2 - access("C"."REGION_ID"="R"."REGION_ID")
   7 - access("D"."LOCATION_ID"="L"."LOCATION_ID")
       filter("D"."LOCATION_ID"="L"."LOCATION_ID")
   9 - access("L"."COUNTRY_ID"="C"."COUNTRY_ID")
27 rows selected

第6行和第8行缩进是最多的。第6行首先执行接下来执行第8行,并将索引全扫描所得到的行编号传递给它的父步骤,第7行。继续按照缩进程度,从缩进量最大的行到最小的行来执行。每一步将行源数据传给其父步骤。直到所有步骤完成为止。

访问与筛选谓语

解释计划输出中最有用的部分之一就是被称为谓语信息的部分。在这个部分中,将会示出ACCESS_PREDICATES和FILTER_PREDICATE列。这两列与计划中的一行,用ID列来指示相关。计划中每个有相关的访问或筛选谓语的运算,在其ID旁边都有一个星号(*)。
访问谓语要么进行索引运算,要么进行联结运算。访问谓语就是一种更直接的访问数据的方法,它只获取表中满足where子句的条件,或者与联结两张表的字段相匹配的数据。

使计划便于阅读

学会把计划当做一段文字描述来进行阅读会非常有帮助。对于很多人来说,将一系列的计划运算转化为一段文字描述能够比其它方法更有助于理解计划执行。
为了生成这个select语句的结果集,DEPARTMENTS表中的数据行将会通过对DEPARTMENTS.DEPARTMENT_ID列进行索引全扫描来访问。通过对LOCATIONS表使用进行全扫描,将会取出按LOCATION_ID进行排序的数据行。然后将这两个数据行合并生成联结后的包含DEPARTMENTS和LOCATIONS表中相匹配数据行的数据集。这个数据行集,可暂称为dept_loc,将与再与countries表相联结,并会迭代取出dept_loc数据表中每一行来在countries表寻找与COUNTRY_ID相匹配的行。这样得到的数据集,暂将其称为dept_loc_city。现在其中包含DEPARTMENTS,LOCATION,countries表中的数据并针会散列化到内存中并与REGIONS表通过REGION_ID列进行联结匹配。这个结果集暂称为dept_loc_city_reg,又将会被散列化到内存中并通过DEPARTMENT_ID列来与EMPLOYEES表进行匹配以生成最终的数据行的结果集。

执行计划

当一条SQL执行时将会生成该语句的实际执行计划。在语句被硬解析后,所选的执行计划就会存到库高速缓存中以便以后重用。可以查询V$SQL_PLAN查看计划运算。V$SQL_PLAN与PALN_TABLE的基本相同。这些附加的列是:ADDRESS,HASH_VALUE,SQL_ID,SQL_ID,PLAN_HASH_VALUE,CHILD_ADDRESS以及CHILD_NUMBER。

查看最近生成的SQL语句

--获取最近执行的SQL语句的V$SQL查询
select /* recentsql */
 sql_id, child_number, hash_value, address, executions, sql_text
  from v$sql
 where parsing_user_id =
       (select user_id from all_users where username = 'SCOTT')
   and command_type in (2, 3, 6, 7, 189)
   and upper(sql_text) not like upper('%recentsql%');

当针对V$SQL执行查询时,你可以看到它们现在被载入到了库高速缓存中,并且每一个都有与之相联结的标识。SQL_ID和CHILD_NUMBER列包含了最常用的获取语句执行计划和执行统计信息的标识信息。

查看相关执行计划

有好几中方法可以用来查看任何之前己执行过的SQL语句保存在库高速缓存中的执行计划。最简单的方法就是使用dbms_xplan.display_cursor函数。

SQL> --使用dbms_xplan.display_cursor函数
SQL> select /*+ gather_plan_statistics */  empno,ename from scott.emp where ename='tom';


执行计划
----------------------------------------------------------
Plan hash value: 3956160932

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    10 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| EMP  |     1 |    10 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------

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

   1 - filter("ENAME"='tom')


统计信息
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          8  consistent gets
          0  physical reads
          0  redo size
        475  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)
          1  rows processed

SQL> set serveroutput off;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));

已选择8行。


执行计划
----------------------------------------------------------
Plan hash value: 3713220770

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

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

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

|   0 | SELECT STATEMENT                  |                |  8168 | 16336 |29   (0)| 00:00:01 |

|   1 |  COLLECTION ITERATOR PICKLER FETCH| DISPLAY_CURSOR |       |       |        |          |

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



统计信息
----------------------------------------------------------
         42  recursive calls
          0  db block gets
        122  consistent gets
          0  physical reads
          0  redo size
        810  bytes sent via SQL*Net to client
        416  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
          8  rows processed

SQL>

注意在查询中使用的gather_plan_statistics,为了为计划抓取行数据源执行统计信息,你必须告诉oracle在语句执行时收集这些信息。行数据源的执行统计信息包括行数,一致性读取次数,物理读取次数,物理写入次数,以及每一个运算在一行数据上的运行时间。可以使用这个提示来一句一句的收集这些信息,或者也可以将statistics_level实例参数设置为all。

使用没有gather_plan_statistics提示的dbms_xplan.display_cursor函数

SQL> select empno,ename from scott.emp where ename='tom';

     EMPNO ENAME
---------- ------------------------------
         8 tom

SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  2zd33t69f63cm, child number 0
-------------------------------------
select empno,ename from scott.emp where ename='tom'

Plan hash value: 3956160932

-------------------------------------------
| Id  | Operation         | Name | E-Rows |
-------------------------------------------
|   0 | SELECT STATEMENT  |      |        |
|*  1 |  TABLE ACCESS FULL| EMP  |      1 |

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

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

   1 - filter("ENAME"='tom')

Note
-----
   - Warning: basic plan statistics not available. These are only collected when:

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

       * hint 'gather_plan_statistics' is used for the statement or
       * parameter 'statistics_level' is set to 'ALL', at session or system level



已选择24行。

可以看到,出现了一个警告信息表明无法获取计划统计信息,并告诉你如何中来收集这些信息。

收集执行计划统计信息

要想准确的知道计划的效果如何,你需要计划的行数据源执行统计信息。这些值可以告诉你计划中的每个运算实际上发生了什么。该数据是从V$SQL_PLAN_STATISTICS_ALL的视图中取出来的。这个视图将计划的每个运算行与一行统计数据联系起来。一个名为V$SQL_PLAN_STATISTICS_ALL的复合视图包括了V$SQL_PLAN中的所有列加上V$SQL_PLAN_STATISTICS中的列以及一些包含内存使用信息的附加列。

--V$SQL_PLAN_STATISTICS_ALL视图描述
desc V$SQL_PLAN_STATISTICS_ALL;

Name                   Type           Nullable Default Comments 
---------------------- -------------- -------- ------- -------- 
ADDRESS                RAW(4)         Y                         
HASH_VALUE             NUMBER         Y                         
SQL_ID                 VARCHAR2(13)   Y                         
PLAN_HASH_VALUE        NUMBER         Y                         
CHILD_ADDRESS          RAW(4)         Y                         
CHILD_NUMBER           NUMBER         Y                         
TIMESTAMP              DATE           Y                         
OPERATION              VARCHAR2(30)   Y                         
OPTIONS                VARCHAR2(30)   Y                         
OBJECT_NODE            VARCHAR2(40)   Y                         
OBJECT#                NUMBER         Y                         
OBJECT_OWNER           VARCHAR2(30)   Y                         
OBJECT_NAME            VARCHAR2(30)   Y                         
OBJECT_ALIAS           VARCHAR2(65)   Y                         
OBJECT_TYPE            VARCHAR2(20)   Y                         
OPTIMIZER              VARCHAR2(20)   Y                         
ID                     NUMBER         Y                         
PARENT_ID              NUMBER         Y                         
DEPTH                  NUMBER         Y                         
POSITION               NUMBER         Y                         
SEARCH_COLUMNS         NUMBER         Y                         
COST                   NUMBER         Y                         
CARDINALITY            NUMBER         Y                         
BYTES                  NUMBER         Y                         
OTHER_TAG              VARCHAR2(35)   Y                         
PARTITION_START        VARCHAR2(64)   Y                         
PARTITION_STOP         VARCHAR2(64)   Y                         
PARTITION_ID           NUMBER         Y                         
OTHER                  VARCHAR2(4000) Y                         
DISTRIBUTION           VARCHAR2(20)   Y                         
CPU_COST               NUMBER         Y                         
IO_COST                NUMBER         Y                         
TEMP_SPACE             NUMBER         Y                         
ACCESS_PREDICATES      VARCHAR2(4000) Y                         
FILTER_PREDICATES      VARCHAR2(4000) Y                         
PROJECTION             VARCHAR2(4000) Y                         
TIME                   NUMBER         Y                         
QBLOCK_NAME            VARCHAR2(30)   Y                         
REMARKS                VARCHAR2(4000) Y                         
OTHER_XML              CLOB           Y                         
EXECUTIONS             NUMBER         Y                         
LAST_STARTS            NUMBER         Y                         
STARTS                 NUMBER         Y                         
LAST_OUTPUT_ROWS       NUMBER         Y                         
OUTPUT_ROWS            NUMBER         Y                         
LAST_CR_BUFFER_GETS    NUMBER         Y                         
CR_BUFFER_GETS         NUMBER         Y                         
LAST_CU_BUFFER_GETS    NUMBER         Y                         
CU_BUFFER_GETS         NUMBER         Y                         
LAST_DISK_READS        NUMBER         Y                         
DISK_READS             NUMBER         Y                         
LAST_DISK_WRITES       NUMBER         Y                         
DISK_WRITES            NUMBER         Y                         
LAST_ELAPSED_TIME      NUMBER         Y                         
ELAPSED_TIME           NUMBER         Y                         
POLICY                 VARCHAR2(10)   Y                         
ESTIMATED_OPTIMAL_SIZE NUMBER         Y                         
ESTIMATED_ONEPASS_SIZE NUMBER         Y                         
LAST_MEMORY_USED       NUMBER         Y                         
LAST_EXECUTION         VARCHAR2(10)   Y                         
LAST_DEGREE            NUMBER         Y                         
TOTAL_EXECUTIONS       NUMBER         Y                         
OPTIMAL_EXECUTIONS     NUMBER         Y                         
ONEPASS_EXECUTIONS     NUMBER         Y                         
MULTIPASSES_EXECUTIONS NUMBER         Y                         
ACTIVE_TIME            NUMBER         Y                         
MAX_TEMPSEG_SIZE       NUMBER         Y                         
LAST_TEMPSEG_SIZE      NUMBER         Y                         

包含与涉及dbms_xplan.display_cursor函数输出相关的统计信息的列均以前缀LAST_开头。当使用ALLSTATS LAST格式选项时,计划就会为其中的每一行显示这些列的值。因此,对于每个运算,你将能够准确的知道:

  • 将会返回多少行(LAST_OUTPUT_ROWS在A-Rows列中给出)
  • 发生了多少次一致性读取(LAST_CR_BUFFER_GETS在Buffers列中给出)
  • 发生了多少次物理读取(LAST_DISK_READS在Reads列中给出)
  • 每个步骤执行的次数(LAST_STARTS在Starts列中给出)

根据所执行的运算不同,还将显示其它的一些列,但上面列出的这些是最常见的。

oracle学习笔记6: SQL执行计划_第2张图片
dbms_xplan.display_cursor的调用签名

如果SQL_ID => null\CURSOR_CHILD_NO => null和FORMAT => ALLSTATS LAST,前两个参数使用空值,表明需要取出上一个执行语句的执行计划。因此可以执行一个语句后,然后执行:

set serveroutput off;
select * from regions2;
select * FROM table(dbms_xplan.display_cursor(null, null, 'ALLSTATS LAST'));

调用dbms_xplan.display_cursor函数之前执行了SQL*Plus命令set serveroutput off。任何时候当你执行一个语句并打开serveroutput,都会隐式地调用dbms_output.如果你没有将serveroutput关闭,那么最后执行的一条语句将会是这个dbms_output的调用。

标识SQL语句以便以后取回计划

如果想取出之前的执行过的一个语句,可以从V$SQL中取出SQL_ID和CHILD_NUMBRER。为了简化寻找正确语句标识过程,尤其是在测试时,可以执行的每个语句上加一个唯一的注释来进行标识。


SQL> select /* km-emptest1 */ empno,ename,job
  2  from emp
  3  where job='saler';

     EMPNO ENAME                          JOB
---------- ------------------------------ --------------------
         1 litao                          saler
         2 liqian                         saler

SQL>
SQL> select sql_id,CHILD_NUMBER,SQL_TEXT from v$sql
  2  where sql_text like '%km-emptest1%';

SQL_ID        CHILD_NUMBER
------------- ------------
SQL_TEXT
--------------------------------------------------------------------------------
ddfaq3mrpn1vy            0
select /* km-emptest1 */ empno,ename,job from emp where job='saler'

86hxc8yg7hrs3            0
select sql_id,CHILD_NUMBER,SQL_TEXT from v$sql where sql_text like '%km-emptest1
%'

fatny27bpcw9v            0
select /* km-emptest1 */ empno,ename from emp

SQL_ID        CHILD_NUMBER
------------- ------------
SQL_TEXT
--------------------------------------------------------------------------------

2t51rxgu9kfk7            0
select sql_id,CHILD_NUMBER,SQL_TEXT from v$sql where sql_text like '%km-emptest1
%'

1hg38zrg7kz3w            0
select /* km-emptest1 */ empno,ename,job from emp

50w1wnmpamkw6            0

SQL_ID        CHILD_NUMBER
------------- ------------
SQL_TEXT
--------------------------------------------------------------------------------
select /* km-emptest1 */ empno,ename,job from emp where job='saler'


已选择6行。

SQL>
SQL> select * from table(dbms_xplan.display_cursor('50w1wnmpamkw6',0,'ALLSTATS LAST'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  50w1wnmpamkw6, child number 0
-------------------------------------
select /* km-emptest1 */ empno,ename,job from emp where job='saler'

Plan hash value: 3956160932

-------------------------------------------
| Id  | Operation         | Name | E-Rows |
-------------------------------------------
|   0 | SELECT STATEMENT  |      |        |
|*  1 |  TABLE ACCESS FULL| EMP  |      1 |

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

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

   1 - filter("JOB"='saler')

注意:查询v$sql时,显示了两条语句,一条是寻找v$sql中的条目所执行的select查询语句,另一个是实际执行的查询。

自动为任何SQL语句取出执行计划

SQL> --自动为任何SQL语句取出执行计划
SQL> select /* KM-EMPTEST2 */ empno,ename
  2  from emp where job='saler';
     EMPNO ENAME
---------- ------------------------------
         1 litao
         2 liqian
SQL> get E:\bjc2016\study\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 order by last_active_time) child_number
      FROM V$SQL 
      WHERE UPPER(SQL_TEXT) LIKE '%&1%'
       and upper(sql_text) not like '%FROM V$SQL WHERE UPPER(SQL_TEXT) LIKE %'
    ) sqlinfo,
       table(dbms_xplan.display_cursor(sqlinfo.sql_id, sqlinfo.child_number, 'ALLSTATS LAST')) xplan;
       
SQL> @'E:\bjc2016\study\pln.sql' KM-EMPTEST2
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  985sj533vz6z7, child number 0
-------------------------------------
select /* KM-EMPTEST2 */ empno,ename from emp where job='saler'
Plan hash value: 3956160932
-------------------------------------------
| Id  | Operation         | Name | E-Rows |
-------------------------------------------
|   0 | SELECT STATEMENT  |      |        |
|*  1 |  TABLE ACCESS FULL| EMP  |      1 |
-------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("JOB"='saler')
Note
-----
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
   - Warning: basic plan statistics not available. These are only collected when
       * hint 'gather_plan_statistics' is used for the statement or
       * parameter 'statistics_level' is set to 'ALL', at session or system leve
24 rows selected

这个脚本返回与输入的模式相匹配的最后执行的一条sql语句的执行计划。

深入理解DBMS_XPLAN

Oracle提供DBMS_XPLAN并且可以用来简化执行计划输出的获取和显示。为了全面地使用该包中的所有步骤和功能,需要对一些回定的视图拥有权限。对SELECT_CATALOG_ROLE的单一授权就可以确保拥有访问所有对象的权限。为了恰当的执行display和display_cursor函数,至少应该用有V$SQL,V$SQL_PLAN,V$SESSION,V$SQL_PLAN_STATISTICS_ALL的选择权限。
dbms_xpaln包最初只有display, 在oracle 12c中, 己有26个函数。这些函数不权可以用来展示解释计划输出,而且可以用来输出存储在自动工作负载信息库auomatic workload repository, AWR,SQL调试集,缓存sql游标以及sql计划基线中的语句计划。
7个主要的用来在上述领域显示计划的表函数分别如下:

  • DISPLAY
  • DISPLAY_CURSOR
  • DISPLAY_AWR
  • DISPLAY_SQLSET
  • DISPLAY_SQL_PATCH_PLAN
  • DISPLAY_SQL_PROFILE_PLAN
  • DISPLAY_SQL_PLAN_BASELINE

这7个函数都返回 DBMS_XPLAN_TYPE_TABLE类型,由一个300字节的字串符组成。这个类型包含含了每个表函数用来动态显示计划表中的列所需的不同格式。这些函数是表函数意味着,当在select语句中使用这些函数时,你必须用table函数将返回类型转换为正确的类型。一个表函数就是一个存储起来,行为与通常对表的查询类似的PL/SQL函数。其好处在于你可以函数中写代码来在数据返回最终结果集之前对其进行数据转换。在查询plan_table或v$sql_plan时,表函数可以实现只输出某个给定的sql语句相关的列所需的所有动态格式,而不必努力创建多个查询来处理不同的需求。
这些表函数中的每个都可以接受format参数作为输入。format参数控制着那些信息将包含在显示输出中。

  • basic只显示运算名称和选项
  • typecal显示相关信息以及在适当的情况下可能的显示选项,如分区和并发使用。这是默认值。
  • serial与typical相同但总是排除并发信息。
  • all在输出中显示最多的信息。
    除了基本的格式参数值以外,还有一些附加的细化选项可以用来定制基值的默认行为。可以使用逗号或空格分隔来声明多个关键字,并使用加号(+)表示包含或使用减号标识(-)表示排除某个特定的显示元素。所有的这些选项都仅显示相关的信息。
    下面是一些可选的关键字。
  • advanced显示与all相同的信息再加上大纲部分和窥视的绑定值部分。
  • alias显示查询块名称、对象别名部分。
  • all显示查询块名称/对象别名部分,谓语部分,以及列投影部分。
  • allstats*与iostats last等价。
  • bytes显示估计的字节数
  • cost显示优化器所计算出的成本信息。
  • iostats*显示游标执行的IO统计信息。
  • last*权显示最后执行的游标执行计划统计信息(默认为all并且是可累积的)。
  • memstats*为内存密集运算如散列联结,排序或一些类型的位图运算显示器内存管理统计信息。
  • 显示注释部分。
  • outline显示大纳部分,将会重新生成计划的一系列提示。
  • parallel显示并行执行信息。
  • partition显示分区裁剪信息。
  • peeked_binds显示绑定变量值信息。
  • predicate显示谓语部分。
  • projection显示列投影部分,每一行中的那些列被传递给其父列以及这些列的大小。
  • remote显示分布式查询信息。
    后面有星号的关键字不能在dispaly函数中使用,因为它们需要使用只有在语句执行后才会在v$sql_plan_statistics_all中存在的信息。

显示使用format参数的选项

SQL> --显示使用format参数的选项
SQL> explain plan for
  2  select * from emp e,dept d
  3  where e.deptno=d.deptno
  4  and e.ename='litao';

已解释。
SQL>
SQL> select * from table(dbms_xplan.display(format=>'all'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2709701336

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

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

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


PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |     1 |    55 |     4   (0)| 00:00:01 |

|   1 |  NESTED LOOPS                |              |       |       | |          |

|   2 |   NESTED LOOPS               |              |     1 |    55 |     4   (0)| 00:00:01 |

|*  3 |    TABLE ACCESS FULL         | EMP          |     1 |    38 |     3   (0)| 00:00:01 |

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

|*  4 |    INDEX UNIQUE SCAN         | SYS_C0010266 |     1 |       |     0   (0)| 00:00:01 |

|   5 |   TABLE ACCESS BY INDEX ROWID| DEPT         |     1 |    17 |     1   (0)| 00:00:01 |

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



PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   3 - SEL$1 / E@SEL$1
   4 - SEL$1 / D@SEL$1
   5 - SEL$1 / D@SEL$1

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


PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
   3 - filter("E"."ENAME"='litao')
   4 - access("E"."DEPTNO"="D"."DEPTNO")

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - (#keys=0) "E"."EMPNO"[NUMBER,22], "E"."ENAME"[VARCHAR2,30],
       "E"."DEPTNO"[NUMBER,22], "E"."JOB"[VARCHAR2,20], "E"."MGR"[NUMBER,22],
       "E"."HIREDATE"[DATE,7], "E"."SAL"[NUMBER,22], "E"."COMM"[NUMBER,22],
       "D"."DEPTNO"[NUMBER,22], "D"."DNAME"[VARCHAR2,30], "D"."LOC"[VARCHAR2,30]
   2 - (#keys=0) "E"."EMPNO"[NUMBER,22], "E"."ENAME"[VARCHAR2,30],

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
       "E"."DEPTNO"[NUMBER,22], "E"."JOB"[VARCHAR2,20], "E"."MGR"[NUMBER,22],
       "E"."HIREDATE"[DATE,7], "E"."SAL"[NUMBER,22], "E"."COMM"[NUMBER,22],
       "D".ROWID[ROWID,10], "D"."DEPTNO"[NUMBER,22]
   3 - "E"."EMPNO"[NUMBER,22], "E"."ENAME"[VARCHAR2,30], "E"."DEPTNO"[NUMBER,22]
,

       "E"."JOB"[VARCHAR2,20], "E"."MGR"[NUMBER,22], "E"."HIREDATE"[DATE,7],
       "E"."SAL"[NUMBER,22], "E"."COMM"[NUMBER,22]
   4 - "D".ROWID[ROWID,10], "D"."DEPTNO"[NUMBER,22]
   5 - "D"."DNAME"[VARCHAR2,30], "D"."LOC"[VARCHAR2,30]

已选择43行。

使用allstats last-cost-bytes参数

--使用allstats last-cost-bytes参数
SQL> explain plan for
  2  select empno,ename from emp e,dept d
  3  where e.deptno=d.deptno
  4  and e.ename='litao';

已解释。

SQL> select * from table(dbms_xplan.display_cursor(null,null,format=>'ALLSTATS LAST -COST -BYTES'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  gfc5y4bb4s05s, child number 0

explain plan for select empno,ename from emp e,dept d where
e.deptno=d.deptno and e.ename='litao'

NOTE: cannot fetch plan for SQL_ID: gfc5y4bb4s05s, CHILD_NUMBER: 0
      Please verify value of SQL_ID and CHILD_NUMBER;
      It could also be that the plan is no longer in cursor cache (check v$sql_p
lan)
已选择9行。

使用+peeked binds参数的输出,显示绑定变量的值

SQL> --使用+peeked binds参数的输出,显示绑定变量的值
SQL> variable v_empno number;
SQL> exec :v_empno :=1;

PL/SQL 过程已成功完成。

SQL> select empno,ename,job,mgr,sal,deptno from emp where empno = :v_empno;

     EMPNO ENAME                          JOB                         MGR
---------- ------------------------------ -------------------- ----------
       SAL     DEPTNO
---------- ----------
         1 litao                          saler                         2      9000          1

SQL> select * from table(dbms_xplan.display_cursor(null,null,format => '+PEEKED_BINDS'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  ahfvq692m3xs2, child number 0
-------------------------------------
select empno,ename,job,mgr,sal,deptno from emp where empno = :v_empno

Plan hash value: 887528266

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

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

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

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

|   0 | SELECT STATEMENT            |              |       |       |     1 (100)|          |

|   1 |  TABLE ACCESS BY INDEX ROWID| EMP          |     1 |    29 |     1   (0)| 00:00:01 |

|*  2 |   INDEX UNIQUE SCAN         | SYS_C0010262 |     1 |       |     0   (0)

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------|          |

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


Peeked Binds (identified by position):
--------------------------------------

   1 - :V_EMPNO (NUMBER): 1


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

   2 - access("EMPNO"=:V_EMPNO)
已选择24行。

使用basic+parallel+predicate参数的输出结果,显示了并行查询执行计划的详细信息

SQL> --使用basic+parallel+predicate参数的输出结果,显示了并行查询执行计划的详细信息
SQL> select /*+ parallel(d,4) parallel (4,4) */
  2  d.dname,avg(e.sal),max(e.sal)
  3  from dept d,emp e
  4  where d.deptno=e.deptno
  5  group by d.dname
  6  order by max(e.sal), avg(e.sal) desc;

DNAME                          AVG(E.SAL) MAX(E.SAL)
------------------------------ ---------- ----------
develop                              9000       9000
sales                                9000       9000
finance                              9000       9000
hr                                   7400       9000

SQL>
SQL> select * from table(dbms_xplan.display_cursor(null,null,'BASIC +PARALLEL +PREDICATE'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select /*+ parallel(d,4) parallel (4,4) */
d.dname,avg(e.sal),max(e.sal) from dept d,emp e where d.deptno=e.deptno
group by d.dname order by max(e.sal), avg(e.sal) desc

Plan hash value: 3127499263

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


PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
| Id  | Operation                             | Name         |    TQ  |IN-OUT| PQ Distrib |

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

|   0 | SELECT STATEMENT                      |              |        |      |
          |

|   1 |  PX COORDINATOR                       |              |        |      |
          |

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

|   2 |   PX SEND QC (ORDER)                  | :TQ10002     |  Q1,02 | P->S | QC (ORDER) |

|   3 |    SORT ORDER BY                      |              |  Q1,02 | PCWP |          |

|   4 |     PX RECEIVE                        |              |  Q1,02 | PCWP |          |

|   5 |      PX SEND RANGE                    | :TQ10001     |  Q1,01 | P->P | R

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
ANGE      |

|   6 |       HASH GROUP BY                   |              |  Q1,01 | PCWP |          |

|   7 |        PX RECEIVE                     |              |  Q1,01 | PCWP |          |

|   8 |         PX SEND HASH                  | :TQ10000     |  Q1,00 | P->P | HASH       |


PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|   9 |          HASH GROUP BY                |              |  Q1,00 | PCWP |          |

|  10 |           NESTED LOOPS                |              |  Q1,00 | PCWP |          |

|  11 |            NESTED LOOPS               |              |  Q1,00 | PCWP |          |

|  12 |             PX BLOCK ITERATOR         |              |  Q1,00 | PCWC |          |

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

|* 13 |              TABLE ACCESS FULL        | EMP          |  Q1,00 | PCWP |          |

|* 14 |             INDEX UNIQUE SCAN         | SYS_C0010266 |  Q1,00 | PCWP |          |

|  15 |            TABLE ACCESS BY INDEX ROWID| DEPT         |  Q1,00 | PCWP |          |

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

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


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

  13 - access(:Z>=:Z AND :Z<=:Z)
  14 - access("D"."DEPTNO"="E"."DEPTNO")


已选择35行。

使用sql监控报告

自从Oracle 11g中引入SQL监控报告以来,就有另一种查看执行计划中的数据源执行统计信息的方法,从而搞清楚时间和资源是如何在特定的sql语句中使用的。它与dbms_xpaln.display_cursor相似,但还具备一些特有的特性。SQL监控报告在可用性方面最值得注意的一点是,即使statistics_level参数设置为typical,也会默认开启监控报告。此外在消耗cpu或io时间超过5秒的语句,以及使用并行执行的语句,都将自动被监控。

oracle学习笔记6: SQL执行计划_第3张图片
dbms_xplan.report_sql_monitor调用的内容

显示一份sql监控报告

--显示一份sql监控报告
select /*+ monitor */ * from employees2 where email like 'S%';
select dbms_sqltune.report_sql_monitor() from dual;

使用计划信息解决问题

确定索引缺失

如果缺少一个索引或某个索引是次优的,可以从计划中看出来。
使用计划信息确定次优索引

--create index hr.EMP_JOB_IX on hr.employees(job_id);
select /* KM1 */
 job_id, department_id, last_name
  from hr.employees
 where job_id = 'SA_REP'
   and department_id is null;
@E:\bjc2016\study\pln KM1;

在索引中增加一列来优化查询

--在索引中增加一列来优化查询
create index hr.emp_job_dept_ix on hr.employees(department_id,job_id) compute statistics;

select /* KM2 */  job_id, department_id, last_name   from hr.employees
where job_id = 'SA_REP'    and department_id is null;

@E:\bjc2016\study\pln KM2;

不管计划有多复杂,要分辩出索引的缺失或者次优索引的方法就是要找具有较小的A-Rows值,也就是与表中的总行数相比较来说比较小的筛选性谓语的全且访问运算。优化后可能通过索引rowid进行父表访问的A-Rows值相比较具有较大的A-Rows值的索引扫描运算。
想提高效率就需要建立更好索引,如果直接将两列都放到索引的定义中去,索引就会只会返回一个行ID,父步骤也就不必访问最终会被舍弃的数据所在的数据块了。
没有索引的情况下,优化器的唯一选择就是进行全表扫描。随着数据的增大,响应时间会持续下降。

--使用计划信息来确定缺少的索引

select /* KM3 */  last_name,phone_number  from hr.employees
where phone_number='650.507.9822';

@E:\bjc2016\study\pln KM3;

column column_name format a22 heading 'Column name';
column index_name format heading 'Index name';
column column_position format 999999999 heading 'Pos#';
column descend format a5 heading 'Order';
column column_expression format a40 heading 'Expression';
break on index_name skip 1;
--检查目前的索引
select lower(b.index_name) index_name,b.COLUMN_POSITION,b.DESCEND,lower(b.COLUMN_NAME) column_name 
from all_ind_columns b
where b.TABLE_OWNER='HR'
and b.TABLE_NAME='EMPLOYEES'
order by b.INDEX_NAME,b.COLUMN_POSITION,b.COLUMN_NAME;

查询 all_ind_columns 视图可以验证存在那些索引,这些索引建立在那些列上。phone_number列上没有索引,因此传化器除了全表扫描筛选相匹配的数据行外,别无选择。


--建立索引来提升性能
create index  hr.emp_phone_ix on hr.employees(phone_number) compute statistics;
set serveroutput off;
select /* KM4 */  last_name,phone_number  from hr.employees
where phone_number='650.507.9822';

@E:\bjc2016\study\pln KM4;

有了索引以后,优化器就选择使用这个索引,访问能够满足查询条件的数据行。现在不需要检验表中所有的行,仅仅通过索引访问了与所需查询的电话号码相匹配的那一行数据。

小结

oracle学习笔记6: SQL执行计划_第4张图片
小结

你可能感兴趣的:(oracle学习笔记6: SQL执行计划)