解读Oracle执行计划(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/seagal890/article/details/82735429

解读Oracle执行计划(一)

以SYS用户身份连接Oracle 12c 数据库,然后,执行下面的语句清空buffer_cache和shared_pool中的数据(生产环境请谨慎使用该语句)

SQL> alter system flush buffer_cache;

系统已更改。

SQL> alter system flush shared_pool;

系统已更改。

SQL>

打开另外一个SQL Plus会话窗口,以SYS用户连接到Oracle 12c数据库,可以是PDB,然后做一些基本的设置,设置 set autotrace traceonly,接着执行一条SQL查询语句:

SQL> set timing on;
SQL> set autotrace traceonly;
SQL> set linesize 200;
SQL> set pagesize 999;
SQL>
关于设置autotrace的说明
  • SET AUTOTRACE OFF:此为默认值,即关闭Autotrace
  • SET AUTOTRACE ON EXPLAIN:只显示执行计划
  • SET AUTOTRACE ON STATISTICS:只显示执行的统计信息
  • SET AUTOTRACE ON:包含执行计划和统计信息
  • SET AUTOTRACE TRACEONLY:与ON相似,但不显示语句的执行结果
SQL> select count(*) from dba_objects order by object_type asc;

产生的执行结果中:

------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
  • ID: 一个序号,但不是执行的先后顺序。执行的先后根据缩进来判断
  • Operation: 当前操作的内容。
  • Rows: 当前操作的Cardinality,Oracle估计当前操作的返回结果集。
  • Cost(CPU):Oracle 计算出来的一个数值(代价),用于说明SQL执行的代价。
  • Time:Oracle 估计当前操作的时间。

在看执行计划的时候,除了看执行计划本身,还需要看谓词和统计信息。 通过整体信息来判断SQL效率。

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

   5 - access("S"."OBJ#"=:B1)
   6 - filter(("O"."TYPE#"<>1 AND "O"."TYPE#"<>2 AND "O"."TYPE#"<>6 OR "O"."TYPE#"=1 AND  NOT
              EXISTS (SELECT 0 FROM "SYS"."OBJ$" "IO","SYS"."TAB$" "T","SYS"."IND$" "I" WHERE
              "I"."OBJ#"=:B1 AND "I"."BO#"="T"."OBJ#" AND BITAND("T"."PROPERTY",36893488147419103232)=368934
              88147419103232 AND "IO"."OBJ#"="I"."BO#" AND "IO"."TYPE#"=2) OR "O"."TYPE#"=2 AND  (SELECT 1
              FROM "SYS"."TAB$" "T" WHERE "T"."OBJ#"=:B2 AND
              BITAND("T"."PROPERTY",36893488147419103232)=0)=1 OR "O"."TYPE#"=6 AND  (SELECT 1 FROM
              "SYS"."SEQ$" "S" WHERE "S"."OBJ#"=:B3 AND (BITAND("S"."FLAGS",1024)=0 OR "S"."FLAGS" IS
              NULL))=1) AND (BITAND("U"."SPARE1",16)=0 OR BITAND("O"."FLAGS",1048576)=1048576 OR
              "O"."TYPE#"<>88 AND  NOT EXISTS (SELECT 0 FROM "SYS"."USER_EDITIONING$" "UE" WHERE
              "TYPE#"=:B4 AND "UE"."USER#"=:B5) OR  EXISTS (SELECT 0 FROM "SYS"."USER_EDITIONING$" "UE"
              WHERE "UE"."TYPE#"=:B6 AND "UE"."USER#"=:B7) AND
              (SYS_CONTEXT('userenv','current_edition_name')='ORA$BASE' AND "U"."TYPE#"<>2 OR "U"."TYPE#"=2
              AND "U"."SPARE2"=TO_NUMBER(SYS_CONTEXT('userenv','current_edition_id')) OR  EXISTS (SELECT 0
              FROM "SYS"."USER$" "U2","SYS"."OBJ$" "O2" WHERE "O2"."OWNER#"="U2"."USER#" AND
              "O2"."TYPE#"=88 AND "O2"."DATAOBJ#"=:B8 AND "U2"."TYPE#"=2 AND
              "U2"."SPARE2"=TO_NUMBER(SYS_CONTEXT('userenv','current_edition_id'))))))
   7 - access("O"."SPARE3"="U"."USER#")
   9 - access("O"."OWNER#"="U"."USER#")
  11 - filter("O"."NAME"<>'_NEXT_OBJECT' AND "O"."NAME"<>'_default_auditing_options_' AND
              "O"."TYPE#"<>10 AND "O"."LINKNAME" IS NULL AND BITAND("O"."FLAGS",128)=0)
  15 - access("I"."OBJ#"=:B1)
  16 - filter("I"."BO#"="T"."OBJ#" AND BITAND("T"."PROPERTY",36893488147419103232)=36893488147
              419103232)
  17 - access("IO"."OBJ#"="I"."BO#" AND "IO"."TYPE#"=2)
       filter("IO"."TYPE#"=2)
  18 - filter(BITAND("T"."PROPERTY",36893488147419103232)=0)
  19 - access("T"."OBJ#"=:B1)
  20 - filter(BITAND("S"."FLAGS",1024)=0 OR "S"."FLAGS" IS NULL)
  21 - access("S"."OBJ#"=:B1)
  22 - filter("TYPE#"=:B1 AND "UE"."USER#"=:B2)
  23 - filter("UE"."TYPE#"=:B1 AND "UE"."USER#"=:B2)
  25 - access("U2"."TYPE#"=2 AND "U2"."SPARE2"=TO_NUMBER(SYS_CONTEXT('userenv','current_editio
              n_id')))
       filter("U2"."TYPE#"=2 AND "U2"."SPARE2"=TO_NUMBER(SYS_CONTEXT('userenv','current_editio
              n_id')))
  26 - access("O2"."DATAOBJ#"=:B1 AND "O2"."TYPE#"=88 AND "O2"."OWNER#"="U2"."USER#")
  27 - access("L"."OWNER#"="U"."USER#")

其中:

Access :
1、通过某种方式定位了需要的数据,然后读取出这些结果集,叫做Access。
2、表示这个谓词条件的值将会影响数据的访问路劲(表还是索引)。

Filter:
1、把所有的数据都访问了,然后过滤掉不需要的数据,这种方式叫做Filter 。
2、表示谓词条件的值不会影响数据的访问路劲,只起过滤的作用。

在谓词中主要注意access,要考虑谓词的条件,使用的访问路径是否正确。

完整的执行结果如下所示

SQL> select count(*) from dba_objects order by object_type asc;

已用时间:  00: 00: 02.85

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

------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |                  |     1 |    13 |   399   (4)| 00:00:01 |
|   1 |  SORT AGGREGATE                   |                  |     1 |    13 |            |          |
|   2 |   VIEW                            | DBA_OBJECTS      | 67084 |   851K|   399   (4)| 00:00:01 |
|   3 |    UNION-ALL                      |                  |       |       |            |          |
|   4 |     TABLE ACCESS BY INDEX ROWID   | SUM$             |     1 |    26 |     0   (0)| 00:00:01 |
|*  5 |      INDEX UNIQUE SCAN            | I_SUM$_1         |     1 |       |     0   (0)| 00:00:01 |
|*  6 |     FILTER                        |                  |       |       |            |          |
|*  7 |      HASH JOIN                    |                  | 72621 |  9928K|   337   (2)| 00:00:01 |
|   8 |       INDEX FULL SCAN             | I_USER2          |   129 |   516 |     1   (0)| 00:00:01 |
|*  9 |       HASH JOIN                   |                  | 72621 |  9644K|   335   (2)| 00:00:01 |
|  10 |        INDEX FULL SCAN            | I_USER2          |   129 |  3096 |     1   (0)| 00:00:01 |
|* 11 |        TABLE ACCESS FULL          | OBJ$             | 72621 |  7942K|   334   (2)| 00:00:01 |
|  12 |      NESTED LOOPS                 |                  |     1 |    32 |     4   (0)| 00:00:01 |
|  13 |       NESTED LOOPS                |                  |     1 |    23 |     3   (0)| 00:00:01 |
|  14 |        TABLE ACCESS BY INDEX ROWID| IND$             |     1 |    10 |     2   (0)| 00:00:01 |
|* 15 |         INDEX UNIQUE SCAN         | I_IND1           |     1 |       |     1   (0)| 00:00:01 |
|* 16 |        TABLE ACCESS CLUSTER       | TAB$             |     1 |    13 |     1   (0)| 00:00:01 |
|* 17 |       INDEX RANGE SCAN            | I_OBJ1           |     1 |     9 |     1   (0)| 00:00:01 |
|* 18 |      TABLE ACCESS CLUSTER         | TAB$             |     1 |    13 |     2   (0)| 00:00:01 |
|* 19 |       INDEX UNIQUE SCAN           | I_OBJ#           |     1 |       |     1   (0)| 00:00:01 |
|* 20 |      TABLE ACCESS BY INDEX ROWID  | SEQ$             |     1 |     8 |     1   (0)| 00:00:01 |
|* 21 |       INDEX UNIQUE SCAN           | I_SEQ1           |     1 |       |     0   (0)| 00:00:01 |
|* 22 |      TABLE ACCESS FULL            | USER_EDITIONING$ |     1 |     6 |     2   (0)| 00:00:01 |
|* 23 |      TABLE ACCESS FULL            | USER_EDITIONING$ |     1 |     6 |     2   (0)| 00:00:01 |
|  24 |      NESTED LOOPS SEMI            |                  |     1 |    29 |     2   (0)| 00:00:01 |
|* 25 |       INDEX SKIP SCAN             | I_USER2          |     1 |    20 |     1   (0)| 00:00:01 |
|* 26 |       INDEX RANGE SCAN            | I_OBJ4           |     1 |     9 |     1   (0)| 00:00:01 |
|* 27 |     HASH JOIN                     |                  |     2 |    14 |     2   (0)| 00:00:01 |
|  28 |      INDEX FULL SCAN              | I_LINK1          |     2 |     6 |     1   (0)| 00:00:01 |
|  29 |      INDEX FULL SCAN              | I_USER2          |   129 |   516 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

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

   5 - access("S"."OBJ#"=:B1)
   6 - filter(("O"."TYPE#"<>1 AND "O"."TYPE#"<>2 AND "O"."TYPE#"<>6 OR "O"."TYPE#"=1 AND  NOT
              EXISTS (SELECT 0 FROM "SYS"."OBJ$" "IO","SYS"."TAB$" "T","SYS"."IND$" "I" WHERE
              "I"."OBJ#"=:B1 AND "I"."BO#"="T"."OBJ#" AND BITAND("T"."PROPERTY",36893488147419103232)=368934
              88147419103232 AND "IO"."OBJ#"="I"."BO#" AND "IO"."TYPE#"=2) OR "O"."TYPE#"=2 AND  (SELECT 1
              FROM "SYS"."TAB$" "T" WHERE "T"."OBJ#"=:B2 AND
              BITAND("T"."PROPERTY",36893488147419103232)=0)=1 OR "O"."TYPE#"=6 AND  (SELECT 1 FROM
              "SYS"."SEQ$" "S" WHERE "S"."OBJ#"=:B3 AND (BITAND("S"."FLAGS",1024)=0 OR "S"."FLAGS" IS
              NULL))=1) AND (BITAND("U"."SPARE1",16)=0 OR BITAND("O"."FLAGS",1048576)=1048576 OR
              "O"."TYPE#"<>88 AND  NOT EXISTS (SELECT 0 FROM "SYS"."USER_EDITIONING$" "UE" WHERE
              "TYPE#"=:B4 AND "UE"."USER#"=:B5) OR  EXISTS (SELECT 0 FROM "SYS"."USER_EDITIONING$" "UE"
              WHERE "UE"."TYPE#"=:B6 AND "UE"."USER#"=:B7) AND
              (SYS_CONTEXT('userenv','current_edition_name')='ORA$BASE' AND "U"."TYPE#"<>2 OR "U"."TYPE#"=2
              AND "U"."SPARE2"=TO_NUMBER(SYS_CONTEXT('userenv','current_edition_id')) OR  EXISTS (SELECT 0
              FROM "SYS"."USER$" "U2","SYS"."OBJ$" "O2" WHERE "O2"."OWNER#"="U2"."USER#" AND
              "O2"."TYPE#"=88 AND "O2"."DATAOBJ#"=:B8 AND "U2"."TYPE#"=2 AND
              "U2"."SPARE2"=TO_NUMBER(SYS_CONTEXT('userenv','current_edition_id'))))))
   7 - access("O"."SPARE3"="U"."USER#")
   9 - access("O"."OWNER#"="U"."USER#")
  11 - filter("O"."NAME"<>'_NEXT_OBJECT' AND "O"."NAME"<>'_default_auditing_options_' AND
              "O"."TYPE#"<>10 AND "O"."LINKNAME" IS NULL AND BITAND("O"."FLAGS",128)=0)
  15 - access("I"."OBJ#"=:B1)
  16 - filter("I"."BO#"="T"."OBJ#" AND BITAND("T"."PROPERTY",36893488147419103232)=36893488147
              419103232)
  17 - access("IO"."OBJ#"="I"."BO#" AND "IO"."TYPE#"=2)
       filter("IO"."TYPE#"=2)
  18 - filter(BITAND("T"."PROPERTY",36893488147419103232)=0)
  19 - access("T"."OBJ#"=:B1)
  20 - filter(BITAND("S"."FLAGS",1024)=0 OR "S"."FLAGS" IS NULL)
  21 - access("S"."OBJ#"=:B1)
  22 - filter("TYPE#"=:B1 AND "UE"."USER#"=:B2)
  23 - filter("UE"."TYPE#"=:B1 AND "UE"."USER#"=:B2)
  25 - access("U2"."TYPE#"=2 AND "U2"."SPARE2"=TO_NUMBER(SYS_CONTEXT('userenv','current_editio
              n_id')))
       filter("U2"."TYPE#"=2 AND "U2"."SPARE2"=TO_NUMBER(SYS_CONTEXT('userenv','current_editio
              n_id')))
  26 - access("O2"."DATAOBJ#"=:B1 AND "O2"."TYPE#"=88 AND "O2"."OWNER#"="U2"."USER#")
  27 - access("L"."OWNER#"="U"."USER#")

Note
-----
   - this is an adaptive plan


统计信息
----------------------------------------------------------
       1043  recursive calls
          0  db block gets
      18914  consistent gets
       1812  physical reads
          0  redo size
        545  bytes sent via SQL*Net to client
        607  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
        193  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>

我们得到了SQL的执行计划和统计信息。
下面对以上的执行计划和统计信息进行解读,来说明关于执行计划的一些基本的概念。

关于统计信息这一部分

统计信息
----------------------------------------------------------
       1043  recursive calls
          0  db block gets
      18914  consistent gets
       1812  physical reads
          0  redo size
        545  bytes sent via SQL*Net to client
        607  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
        193  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>
  • recursive calls
    有时为了执行用户发出的一个sql语句,Oracle必须执行一些额外的语句,我们将这些额外的语句称之为”recursive calls”或”recursive SQL statements”。如当一个DDL语句发出后,ORACLE总是隐含的发出一些recursive SQL语句,来修改数据字典信息,以便用户可以成功的执行该DDL语句。当需要的数据字典信息没有在共享内存中时,经常会发生Recursive calls,这些Recursive calls会将数据字典信息从硬盘读入内存中。用户不比关心这些recursive SQL语句的执行情况,在需要的时候,ORACLE会自动的在内部执行这些语句。当然DML语句与SELECT都可能引起recursive SQL。简单的说,我们可以将触发器视为recursive SQL。

  • db block gets
    又叫current mode , 不管这个块上的数据是否可能存在 before image ,也就是说不管是否存在回滚数据,只看见当前最新块的数据,即使别人正在更新,也看不见别人更新状态的数据,比如dml的时候就不需要看见别人更改前的数据,而是看见正在更改的,当然同时,若操作相同数据则被lock住。也就是说一次查询中看见的数据可能不在同一个时间点上,比如一个大的dml,当dml 开始更新一个非常大的表后,这个表更新的过程中,有一个进程去把该表末尾的一个记录更新了,然后这个大更新抵达该记录的时候会被阻塞的,若该进程事物提交,则大更新会覆盖该事务的更新,也就是说,这个大更新所看见的数据是当前的,不具有时间点的一致性,所以叫 current mode。

  • consistent gets
    consistent_gets是从回滚段中读到的前映(或叫读取一致性影象), 看见的数据是查询开始的时间点的,所以若存在block在查询开始后发生了变化的情况,则必须产生 before image 然后读数据,这就是一致读的含义。

    一般查询就是表示 consistent gets (query mode),因为查询要保证所获取的数据的时间点的一致性,所以叫一致读,即使是从当前buffer获得的数据,也叫 consistent gets ,这仅仅表达一种模式一种期望,并不表示真实的是从当前buffer 获得还是从回滚段获取数据产生的 bufore image 。

  • physical reads:
    Total number of data blocks read from disk. This number equals the value of “physical reads direct” plus all reads into buffer cache.
    (Physical Reads:实例启动后,从磁盘读到Buffer Cache数据块数量)

    物理读就是从磁盘上读取数据块的数量,其产生的主要原因是:

    (1) 在数据库高速缓存中不存在这些块

    (2) 全表扫描

    (3) 磁盘排序

它们三者之间的关系大致可概括为:

逻辑读指的是Oracle从内存读到的数据块数量。一般来说是’consistent gets’ + ‘db block gets’。当在内存中找不到所需的数据块的话就需要从磁盘中获取,于是就产生了’physical reads’。

  • redo size

  • bytes sent via SQL*Net to client
    Total number of bytes sent to the client from the foreground processes.

  • SQL*Net roundtrips to/from client
    Total number of Oracle Net messages sent to and received from the client.
    Oracle Net 是把Oracle网络粘合起来的粘合剂。它负责处理客户到服务器和服务器到客户通信,

  • sorts(memory)
    在内存中排序
    Number of sort operations that were performed completely in memory and did not require any disk writes

    You cannot do much better than memory sorts, except maybe no sorts at all. Sorting is usually caused by selection criteria specifications within table join SQL operations.

  • sorts(disk)
    Number of sort operations that required at least one disk write. Sorts that require I/O to disk are quite resource intensive. Try increasing the size of the initialization parameter SORT_AREA_SIZE.

    所有的sort都是优先在memory中做的,当要排序的内容太多,在sort area中放不下的时候,会需要临时表空间,产生sorts(disk)

  • row processed
    The number of rows processed.

关于物理读

physical reads通常是我们最关心的,如果这个值很高,说明要从磁盘请求大量的数据到Buffer Cache里,通常意味着系统里存在大量全表扫描的SQL语句,这会影响到数据库的性能,因此尽量避免语句做全表扫描,对于全表扫描的SQL语句,建议增 加相关的索引,优化SQL语句来解决。

关于physical reads ,db block gets 和consistent gets这三个参数之间有一个换算公式:

数据缓冲区的使用命中率=1 - ( physical reads / (db block gets + consistent gets) )。

SQL> SELECT name, value FROM v$sysstat WHERE name IN ('db block gets', 'consistent gets','physical read

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
db block gets                                                           302
consistent gets                                                      107926
physical reads                                                         4208

已用时间:  00: 00: 00.02

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

-------------------------------------------------------------------------------
| Id  | Operation        | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |            |     3 |   120 |     0   (0)| 00:00:01 |
|*  1 |  FIXED TABLE FULL| X$KSUSGSTA |     3 |   120 |     0   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   1 - filter(("KSUSDNAM"='consistent gets' OR "KSUSDNAM"='db block
              gets' OR "KSUSDNAM"='physical reads') AND ("CON_ID"=0 OR "CON_ID"=3)
              AND "INST_ID"=USERENV('INSTANCE'))


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

SQL> select 1-(4208/(302+107926)) from dual;

1-(4208/(302+107926))
---------------------
           .961119119

已用时间:  00: 00: 00.02

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

-----------------------------------------------------------------
| Id  | Operation        | Name | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT |      |     1 |     2   (0)| 00:00:01 |
|   1 |  FAST DUAL       |      |     1 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------


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

(未完,待续)

猜你喜欢

转载自blog.csdn.net/seagal890/article/details/82735429