查询执行计划 -- 解读执行计划

参考文档:《Oracle® Database SQL Tuning Guide》

5,解读执行计划

        执行计划以操作树的形式展现。

5.1,阅读执行计划:basic

        这里使用EXPLAIN PLAN示例来说明执行计划。使用以下查询语句来显示执行计划:

SELECT PLAN_TABLE_OUTPUT 
  FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'statement_id','BASIC'));

        下面的计划显示了select语句的执行。表employees的访问方式是全表扫描。表Employees中的每一行都有被访问,并对每一行都做了where子句条件的评估:

EXPLAIN PLAN 
  SET statement_id = 'ex_plan1' FOR
  SELECT phone_number 
  FROM   employees
  WHERE  phone_number LIKE '650%';
---------------------------------------
| Id  | Operation         | Name      |
---------------------------------------
|   0 | SELECT STATEMENT  |           |
|   1 |  TABLE ACCESS FULL| EMPLOYEES |
---------------------------------------

        下面的计划显示了select语句的执行。在本例中,数据库对索引EMP_name_ix进行范围扫描以评估WHERE子句条件:

EXPLAIN PLAN 
  SET statement_id = 'ex_plan2' FOR
  SELECT last_name 
  FROM   employees
  WHERE  last_name LIKE 'Pe%';
SELECT PLAN_TABLE_OUTPUT 
  FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, 'ex_plan2','BASIC'));
----------------------------------------
| Id  | Operation        | Name        |
----------------------------------------
|   0 | SELECT STATEMENT |             |
|   1 |  INDEX RANGE SCAN| EMP_NAME_IX |
----------------------------------------

5.2,阅读执行计划:advanced

       在某些情况下,执行计划可能很复杂,而且很难阅读。

5.2.1,阅读自适应查询计划

        自适应优化是优化器的一个特性,它使优化器能够根据运行时的统计信息调整计划。所有自适应机制都可以为SQL语句执行不同于默认计划的最终计划。

        adaptive query plan 在当前语句执行期间选择子计划,而automatic reoptimization 是在当前语句执行完后修改计划。

        可以根据计划的Notes部分中的注释确定数据库是否对SQL语句使用自适应查询优化。注释指明了行源是否是动态的,或者automatic reoptimization是否适合计划。

        做如下假设:

- STATISTICS_LEVEL参数设置为ALL
- 数据库使用默认设置进行自适应执行。
- 用户oe希望发出以下独立的查询:

SELECT o.order_id, v.product_name
FROM   orders o,
       (  SELECT order_id, product_name
          FROM   order_items o, product_information p
          WHERE  p.product_id = o.product_id
          AND    list_price < 50
          AND    min_price < 40  ) v
WHERE  o.order_id = v.order_id;

SELECT product_name
FROM   order_items o, product_information p  
WHERE  o.unit_price = 15 
AND    quantity > 1
AND    p.product_id = o.product_id;

- 在执行每个查询之前,希望查询dbms_xplan.display_plan以查看默认计划,即优化器在应用其自适应机制之前选择的计划。
- 在执行每个查询之后,希望查询dbms_xplan.display_cursor,以查看最终计划和自适应查询计划。 
- sys用户为oe用户进行的如下授权:

GRANT SELECT ON V_$SESSION TO oe;
GRANT SELECT ON V_$SQL TO oe;
GRANT SELECT ON V_$SQL_PLAN TO oe;
GRANT SELECT ON V_$SQL_PLAN_STATISTICS_ALL TO oe;

        得到的自适应优化结果如下:

1)启动SQL*Plus,然后使用用户oe连接到数据库。
2)查询orders表。使用如下语句:

SELECT o.order_id, v.product_name
FROM   orders o,
       (  SELECT order_id, product_name
          FROM   order_items o, product_information p
          WHERE  p.product_id = o.product_id
          AND    list_price < 50
          AND    min_price < 40  ) v
WHERE  o.order_id = v.order_id;

3)查看游标中的计划。使用如下语句:

SET LINESIZE 165
SET PAGESIZE 0
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(FORMAT=>'+ALLSTATS'));

        在如下计划中,优化器选择一个嵌套循环连接。原始优化器估计值显示在E-Rows列中,而执行期间收集的实际统计数据显示在 A-Rows列中。在merge_join接操作中,估计行数与实际行数之间的差异非常显著。

--------------------------------------------------------------------------------------------
|Id| Operation             | Name          |Start|E-Rows|A-Rows|A-Time|Buff|OMem|1Mem|O/1/M|
--------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT      |                |   1|   | 269|00:00:00.09|1338|    |    |     |
| 1|  NESTED LOOPS         |                |   1|  1| 269|00:00:00.09|1338|    |    |     |
| 2|   MERGE JOIN CARTESIAN|                |   1|  4|9135|00:00:00.03|  33|    |    |     |
|*3|    TABLE ACCESS FULL  |PRODUCT_INFORMAT|   1|  1|  87|00:00:00.01|  32|    |    |     |
| 4|    BUFFER SORT        |                |  87|105|9135|00:00:00.01|   1|4096|4096|1/0/0|
| 5|     INDEX FULL SCAN   | ORDER_PK       |   1|105| 105|00:00:00.01|   1|    |    |     |
|*6|   INDEX UNIQUE SCAN   | ORDER_ITEMS_UK |9135|  1| 269|00:00:00.03|1305|    |    |     |
--------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - filter(("MIN_PRICE"<40 AND "LIST_PRICE"<50))
   6 - access("O"."ORDER_ID"="ORDER_ID" AND "P"."PRODUCT_ID"="O"."PRODUCT_ID")

4)使用第2步中的语句查询orders表。

5)使用第3步中的语句查看游标中的计划。

        下面的示例显示优化器使用哈希联接选择了不同的计划。Note部分显示优化器使用统计反馈来调整第二次执行查询时的成本估计,从而说明automatic reoptimization。

--------------------------------------------------------------------------------------------
|Id| Operation              |Name    |Start|E-Rows|A-Rows|A-Time|Buff|Reads|OMem|1Mem|O/1/M|
--------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT       |               | 1 |   |269|00:00:00.02|60|1|     |     |     |
| 1|  NESTED LOOPS          |               | 1 |269|269|00:00:00.02|60|1|     |     |     |
|*2|   HASH JOIN            |               | 1 |313|269|00:00:00.02|39|1|1000K|1000K|1/0/0|
|*3|    TABLE ACCESS FULL   |PRODUCT_INFORMA| 1 | 87| 87|00:00:00.01|15|0|     |     |     |
| 4|    INDEX FAST FULL SCAN|ORDER_ITEMS_UK | 1 |665|665|00:00:00.01|24|1|     |     |     |
|*5|   INDEX UNIQUE SCAN    |ORDER_PK       |269|  1|269|00:00:00.01|21|0|     |     |     |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("P"."PRODUCT_ID"="O"."PRODUCT_ID")
   3 - filter(("MIN_PRICE"<40 AND "LIST_PRICE"<50))
   5 - access("O"."ORDER_ID"="ORDER_ID")
 
Note
-----
   - statistics feedback used for this statement

6)查询V$SQL来确认性能改善。

        下面的查询显示了这两个语句的性能:

SELECT CHILD_NUMBER, CPU_TIME, ELAPSED_TIME, BUFFER_GETS
FROM   V$SQL
WHERE  SQL_ID = 'gm2npz344xqn8';
 
CHILD_NUMBER   CPU_TIME ELAPSED_TIME BUFFER_GETS
------------ ---------- ------------ -----------
           0      92006       131485        1831
           1      12000        24156          60

        执行的第二条语句(child number 1)使用了统计反馈。CPU时间、运行时间和buffer gets 都显著降低。

7)解析查询表order_items的计划。使用如下语句:

EXPLAIN PLAN FOR
  SELECT product_name 
  FROM   order_items o, product_information p  
  WHERE  o.unit_price = 15
  AND    quantity > 1  
  AND    p.product_id = o.product_id

8)查看 plan table中的计划。使用如下语句:

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

        显示信息如下:

-------------------------------------------------------------------------------
|Id| Operation                 | Name             |Rows|Bytes|Cost (%CPU)|Time|
-------------------------------------------------------------------------------
| 0| SELECT STATEMENT             |                      |4|128|7 (0)|00:00:01|
| 1|  NESTED LOOPS                |                      | |   |     |        |
| 2|   NESTED LOOPS               |                      |4|128|7 (0)|00:00:01|
|*3|    TABLE ACCESS FULL         |ORDER_ITEMS           |4|48 |3 (0)|00:00:01|
|*4|    INDEX UNIQUE SCAN         |PRODUCT_INFORMATION_PK|1|   |0 (0)|00:00:01|
| 5|   TABLE ACCESS BY INDEX ROWID|PRODUCT_INFORMATION   |1|20 |1 (0)|00:00:01|
-------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - filter("O"."UNIT_PRICE"=15 AND "QUANTITY">1)
   4 - access("P"."PRODUCT_ID"="O"."PRODUCT_ID")

        在上面的执行计划中,优化器选择的是嵌套循环连接。

9)运行前面解析过的查询:

SELECT product_name 
FROM   order_items o, product_information p  
WHERE  o.unit_price = 15
AND    quantity > 1  
AND    p.product_id = o.product_id

10)查看游标中的计划。

SET LINESIZE 165
SET PAGESIZE 0
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(FORMAT=>'+ADAPTIVE'));

        样例输出如下。根据在运行时收集的统计信息(步骤4),优化器选择了哈希联接而不是嵌套循环联接。破折号(-)表示优化器考虑过但最终没有选择的嵌套循环计划中的步骤。该转变说明了自适应查询计划功能。 

-------------------------------------------------------------------------------
|Id | Operation                     | Name     |Rows|Bytes|Cost(%CPU)|Time    |
-------------------------------------------------------------------------------
|  0| SELECT STATEMENT              |                     |4|128|7(0)|00:00:01|
| *1|  HASH JOIN                    |                     |4|128|7(0)|00:00:01|
|- 2|   NESTED LOOPS                |                     | |   |    |        |
|- 3|    NESTED LOOPS               |                     | |128|7(0)|00:00:01|
|- 4|     STATISTICS COLLECTOR      |                     | |   |    |        |
| *5|      TABLE ACCESS FULL        | ORDER_ITEMS         |4| 48|3(0)|00:00:01|
|-*6|     INDEX UNIQUE SCAN         | PRODUCT_INFORMATI_PK|1|   |0(0)|00:00:01|
|- 7|    TABLE ACCESS BY INDEX ROWID| PRODUCT_INFORMATION |1| 20|1(0)|00:00:01|
|  8|   TABLE ACCESS FULL           | PRODUCT_INFORMATION |1| 20|1(0)|00:00:01|
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("P"."PRODUCT_ID"="O"."PRODUCT_ID")
   5 - filter("O"."UNIT_PRICE"=15 AND "QUANTITY">1)
   6 - access("P"."PRODUCT_ID"="O"."PRODUCT_ID")
Note
-----
   - this is an adaptive plan (rows marked '-' are inactive)

5.2.2,使用EXPLAIN PLAN查看并行执行

        在某些重要方面,并行查询的计划不同于串行查询的计划。

5.2.2.1,关于 EXPLAIN PLAN与并行查询

        在选择驱动表时,调优并行查询和非并行查询开始时看起来非常相似,但其实它们的选择规则是不同的。

        在串行情况下,在应用限制条件后,最佳驱动表产生的行数最少。数据库使用非唯一索引将少量行联接到较大的表中。

        如下为 由customer、account、transaction组成的表层级结构:

查询执行计划 -- 解读执行计划_第1张图片

        在本例中,customer是最小的表,transaction是最大的表。典型的OLTP查询检索有关特定客户帐户的事务信息。查询从customer表驱动。目标是最小化逻辑I/O,这通常会最小化对包括物理I/O和CPU时间在内的这类重要资源的使用。

        对于并行查询,驱动表通常是最大的表。在这种情况下,使用并行查询并不高效,因为每个表都只访问了少量数据行。但是,如果要确认上个月进行某种类型交易的所有客户呢?从transaction表驱动会更高效,因为customer表上不存在限制条件。数据库将把transaction表中的行联接到account表,然后最终将结果集联接到customer表。在这种情况下,account和customer表上使用的可能是高度选择性的主键或唯一索引,而不是第一个查询中使用的非唯一索引。因为transaction表很大,并且列不是选择性的,所以使用transaction表作为驱动表来进行并行查询会更好一点。

        并行操作包含如下这些:

- PARALLEL_TO_PARALLEL
- PARALLEL_TO_SERIAL
        当查询协调器使用来自并行操作的行时,一定会有PARALLEL_TO_SERIAL操作步骤。此查询中未发生的一种操作类型是SERIAL操作。如果发生这些类型的操作,那么考虑让它们执行并行操作以提高性能,因为它们也是潜在的瓶颈。 
- PARALLEL_FROM_SERIAL
- PARALLEL_TO_PARALLEL
        如果每个步骤中的工作负载几乎相等,则PARALLEL_TO_PARALLEL操作通常产生最佳性能。
- PARALLEL_COMBINED_WITH_CHILD
- PARALLEL_COMBINED_WITH_PARENT
       当数据库同时执行一个步骤与它的父步骤时,会产生PARALLEL_COMBINED_WITH_PARENT操作。

        如果一个并行步骤生成许多行,那么QC可能无法在生成数据行时快速地使用这些行。我们几乎无法改善这种情况。

5.2.2.2,使用EXPLAIN PLAN查看并行查询:example

       当使用带有并行查询的EXPLAIN PLAN时,数据库编译并执行一个并行计划。此计划是通过在QC计划中分配针对并行支持的行源,从串行计划派生而来的。

        设置为PQ模式的两个并行执行服务器所需的表队列行源(px send和px receive)、粒度迭代器和缓冲区排序直接插入到并行计划中。对于并行执行的所有并行执行服务器或串行执行的QC,此计划是相同的计划。

        如下例子演示使用EXPLAIN   PLAN进行并行查询:

CREATE TABLE emp2 AS SELECT * FROM employees;

ALTER TABLE emp2 PARALLEL 2;

EXPLAIN PLAN FOR
  SELECT SUM(salary) 
  FROM   emp2 
  GROUP BY department_id;

SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());
------------------------------------------------------------------------------------------------
| Id  | Operation              | Name     | Rows| Bytes |Cost %CPU|    TQ  |IN-OUT| PQ Distrib |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT         |          | 107 |  2782 |  3 (34) |        |      |            |
| 1 |  PX COORDINATOR          |          |     |       |         |        |      |            |
| 2 |   PX SEND QC (RANDOM)    | :TQ10001 | 107 |  2782 |  3 (34) |  Q1,01 | P->S | QC (RAND)  |
| 3 |    HASH GROUP BY         |          | 107 |  2782 |  3 (34) |  Q1,01 | PCWP |            |
| 4 |     PX RECEIVE           |          | 107 |  2782 |  3 (34) |  Q1,01 | PCWP |            |
| 5 |      PX SEND HASH        | :TQ10000 | 107 |  2782 |  3 (34) |  Q1,00 | P->P | HASH       |
| 6 |       HASH GROUP BY      |          | 107 |  2782 |  3 (34) |  Q1,00 | PCWP |            |
| 7 |        PX BLOCK ITERATOR |          | 107 |  2782 |  2 (0)  |  Q1,00 | PCWP |            |
| 8 |         TABLE ACCESS FULL| EMP2     | 107 |  2782 |  2 (0)  |  Q1,00 | PCWP |            |
------------------------------------------------------------------------------------------------

        一组并行执行服务器并行扫描EMP2,而第二组执行GROUP BY操作的聚合。PX BLOCK ITERATOR行源表示将表EMP2拆分为多个部分,以在并行执行服务器之间划分扫描工作负载。px send和px receive行源表示将两组并行执行服务器连接起来的管道,因为行从并行扫描中向上流动,通过哈希表队列重新分区,然后在顶部集合上读取并聚合。PX SEND QC行源表示以随机(rand)顺序发送给QC的聚合值。px coordinator行源表示控制和调度计划树中出现在它下面的并行计划的QC或Query Coordinator。 

5.2.3,用 EXPLAIN PLAN查看位图索引

        在EXPLAIN PLAN输出的信息中,使用位图索引的索引行源会用BITMAP来表示索引类型。

        注意:使用了位图连接索引的查询指明了位图连接索引的访问路径。位图连接索引和位图索引的操作是一样的。

        在下面的例子中,谓词c1=2生成一个位图,从中可以执行减法。从该位图中减去位图中c2=6的位。另外,减去位图中c2为空的位,解释了为什么计划中有两个MINUS行源。除非列具有非空约束,否则NULL减法对于语义正确性是必需的。TO ROWIDS选项生成表访问所需的rowids。 

EXPLAIN PLAN FOR  SELECT * 
  FROM   t
  WHERE  c1 = 2 
  AND    c2 <> 6 
  OR     c3 BETWEEN 10 AND 20;

SELECT STATEMENT
   TABLE ACCESS T BY INDEX ROWID
      BITMAP CONVERSION TO ROWID
         BITMAP OR
            BITMAP MINUS
               BITMAP MINUS
                  BITMAP INDEX C1_IND SINGLE VALUE
                  BITMAP INDEX C2_IND SINGLE VALUE
               BITMAP INDEX C2_IND SINGLE VALUE
            BITMAP MERGE
               BITMAP INDEX C3_IND RANGE SCAN

5.2.4,使用EXPLAIN PLAN查看结果缓存

        当查询包含result_cache提示时,resultcache操作符将插入到执行计划中。 

        执行如下查询:

SELECT /*+ result_cache */ deptno, avg(sal) 
FROM   emp 
GROUP BY deptno;

        使用EXPLAIN PLAN查看SQL的执行计划:

EXPLAIN PLAN FOR 
  SELECT /*+ result_cache */ deptno, avg(sal) 
  FROM emp 
  GROUP BY deptno;

SELECT PLAN_TABLE_OUTPUT FROM TABLE (DBMS_XPLAN.DISPLAY());

        EXPLAIN PLAN输出信息如下:

--------------------------------------------------------------------------------
|Id| Operation          | Name                     |Rows|Bytes|Cost(%CPU)|Time |
--------------------------------------------------------------------------------
|0| SELECT STATEMENT    |                          | 11 | 77 | 4 (25)| 00:00:01|
|1|  RESULT CACHE       |b06ppfz9pxzstbttpbqyqnfbmy|    |    |       |         |
|2|   HASH GROUP BY     |                          | 11 | 77 | 4 (25)| 00:00:01|
|3|    TABLE ACCESS FULL| EMP                      |107 | 749| 3 (0) | 00:00:01|
--------------------------------------------------------------------------------

        在EXPLAIN PLAN中,ResultCache操作符是通过b06ppfz9pxzstbttpbqyqnfbmy这个CacheId来识别的。可以通过CacheId来查询V$RESULT_CACHE_OBJECTS中的相关信息。

5.2.5,使用EXPLAIN PLAN查看分区对象

        使用EXPLAIN PLAN确定Oracle数据库如何访问特定查询的分区对象。修剪后需要访问的分区显示在“PARTITION START”和“PARTITION STOP”列中。范围分区的行源名称是PARTITION RANGE。哈希分区的行源名称是PARTITION HASH。如果一个被联接的表中的计划表的DISTRIBUTION列包含PARTITION(KEY),则使用部分智能分区连接来实现联接。如果一个被联接的表在其联接列上进行了分区,并且表被并行化,则可以进行部分智能分区联接。在EXPLAIN PLAN的执行计划信息中,如果分区行源出现在连接行源之前,则使用全局智能分区连接来实现连接。只有当两个被联接的表在各自的联接列上进行等分时,才能进行全局智能分区联接。下面是几种类型分区的执行计划示例。 

5.2.5.1,使用EXPLAIN PLAN显示范围和散列分区:examples

        这个例子演示了对在hire_date列上按范围分区的emp_range表进行修剪:

CREATE TABLE emp_range 
PARTITION BY RANGE(hire_date) 
( 
  PARTITION emp_p1 VALUES LESS THAN (TO_DATE('1-JAN-1992','DD-MON-YYYY')),
  PARTITION emp_p2 VALUES LESS THAN (TO_DATE('1-JAN-1994','DD-MON-YYYY')),
  PARTITION emp_p3 VALUES LESS THAN (TO_DATE('1-JAN-1996','DD-MON-YYYY')),
  PARTITION emp_p4 VALUES LESS THAN (TO_DATE('1-JAN-1998','DD-MON-YYYY')),
  PARTITION emp_p5 VALUES LESS THAN (TO_DATE('1-JAN-2001','DD-MON-YYYY')) 
) 
AS SELECT * FROM employees; 


EXPLAIN PLAN FOR 
  SELECT * FROM emp_range; 


--------------------------------------------------------------------
|Id| Operation           | Name      |Rows| Bytes|Cost|Pstart|Pstop|
--------------------------------------------------------------------
| 0| SELECT STATEMENT    |           |  105| 13965 | 2 |   |       |
| 1|  PARTITION RANGE ALL|           |  105| 13965 | 2 | 1 |     5 |
| 2|   TABLE ACCESS FULL | EMP_RANGE |  105| 13965 | 2 | 1 |     5 |
--------------------------------------------------------------------

        数据库在表访问行源之上创建一个分区行源。它遍历要访问的一组分区。在本例中,由于没有使用谓词进行修剪,分区迭代器覆盖所有分区(选项ALL)。PLAN_TABLE表的PARTITION_START和PARTITION_STOP列显示了对1到5之间所有分区的访问。

        再看下面的例子:

EXPLAIN PLAN FOR 
  SELECT * 
  FROM   emp_range 
  WHERE  hire_date >= TO_DATE('1-JAN-1996','DD-MON-YYYY');


-----------------------------------------------------------------------
| Id | Operation                | Name   |Rows|Bytes|Cost|Pstart|Pstop|
-----------------------------------------------------------------------
|  0 | SELECT STATEMENT         |         | 3 | 399 |   2 |     |     |
|  1 |  PARTITION RANGE ITERATOR|         | 3 | 399 |   2 |   4 |   5 |
| *2 |   TABLE ACCESS FULL      |EMP_RANGE| 3 | 399 |   2 |   4 |   5 |
-----------------------------------------------------------------------

       在上面的示例中,数据库在谓词中通过hire_date字段做了分区修剪,因此分区行源只需要遍历分区4和5。

        最后再看一个例子:

EXPLAIN PLAN FOR 
  SELECT *
  FROM   emp_range
  WHERE  hire_date < TO_DATE('1-JAN-1992','DD-MON-YYYY'); 


-----------------------------------------------------------------------
| Id  | Operation            | Name      |Rows|Bytes|Cost|Pstart|Pstop|
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT       |           |    1 |   133 | 2 |   |   |
|   1 |  PARTITION RANGE SINGLE|           |    1 |   133 | 2 | 1 | 1 |
|*  2 |   TABLE ACCESS FULL    | EMP_RANGE |    1 |   133 | 2 | 1 | 1 |
-----------------------------------------------------------------------

        在上面示例中,只有分区1在编译时被访问和知道;因此,不需要分区行源。

        注意:除了分区行源名称是PARTITION HASH而不是PARTITION RANGE外,Oracle数据库为哈希分区对象显示相同的信息。此外,对于哈希分区,只能使用相等或列表内谓词进行修剪。

5.2.5.2, 使用复合分区对象修剪信息:examples

        为了说明Oracle数据库如何显示复合分区对象的修剪信息,考虑使用表emp_comp。它在hiredate上使用范围分区,并在deptno上使用哈希子分区。

CREATE TABLE emp_comp PARTITION BY RANGE(hire_date) 
      SUBPARTITION BY HASH(department_id) SUBPARTITIONS 3 
( 
PARTITION emp_p1 VALUES LESS THAN (TO_DATE('1-JAN-1992','DD-MON-YYYY')),
PARTITION emp_p2 VALUES LESS THAN (TO_DATE('1-JAN-1994','DD-MON-YYYY')),
PARTITION emp_p3 VALUES LESS THAN (TO_DATE('1-JAN-1996','DD-MON-YYYY')),
PARTITION emp_p4 VALUES LESS THAN (TO_DATE('1-JAN-1998','DD-MON-YYYY')),
PARTITION emp_p5 VALUES LESS THAN (TO_DATE('1-JAN-2001','DD-MON-YYYY')) 
) 
AS SELECT * FROM employees; 


EXPLAIN PLAN FOR 
  SELECT * FROM emp_comp; 


-----------------------------------------------------------------------
|Id| Operation           | Name     | Rows  | Bytes |Cost|Pstart|Pstop|
-----------------------------------------------------------------------
| 0| SELECT STATEMENT    |          | 10120 |  1314K| 78 |    |       |
| 1|  PARTITION RANGE ALL|          | 10120 |  1314K| 78 |  1 |     5 |
| 2|   PARTITION HASH ALL|          | 10120 |  1314K| 78 |  1 |     3 |
| 3|    TABLE ACCESS FULL| EMP_COMP | 10120 |  1314K| 78 |  1 |    15 |
-----------------------------------------------------------------------

        上面例子展示了Oracle数据库访问组合对象的所有分区的所有子分区时的计划。为此,数据库使用两个分区行源:一个范围分区行源遍历分区,一个哈希分区行源遍历每个访问分区的子分区。

        在上面示例中,由于数据库没有执行分区修剪,因此范围分区行源遍历1到5分区。在每个分区中,哈希分区行源遍历当前分区的1到3子分区。因此,table access行源访问子分区1到15。换句话说,数据库访问组合对象的所有子分区。

EXPLAIN PLAN FOR 
  SELECT * 
  FROM   emp_comp 
  WHERE  hire_date = TO_DATE('15-FEB-1998', 'DD-MON-YYYY'); 


-----------------------------------------------------------------------
| Id | Operation              | Name    |Rows|Bytes |Cost|Pstart|Pstop|
-----------------------------------------------------------------------
|  0 | SELECT STATEMENT       |          | 20 | 2660 | 17 |     |     |
|  1 |  PARTITION RANGE SINGLE|          | 20 | 2660 | 17 |   5 |   5 |
|  2 |   PARTITION HASH ALL   |          | 20 | 2660 | 17 |   1 |   3 |
|* 3 |    TABLE ACCESS FULL   | EMP_COMP | 20 | 2660 | 17 |  13 |  15 |
-----------------------------------------------------------------------

        在上面的示例中,只访问了第5个分区。这个分区在编译时是已知的,因此数据库不需要在计划中显示它。hash partition行源显示对该分区内所有子分区的访问;也就是说,子分区1到3转换为emp_comp表的子分区13到15。

        再看下面的示例:

EXPLAIN PLAN FOR 
  SELECT * 
  FROM   emp_comp 
  WHERE  department_id = 20; 


------------------------------------------------------------------------
| Id | Operation              |Name    |Rows | Bytes |Cost|Pstart|Pstop|
------------------------------------------------------------------------
|  0 | SELECT STATEMENT       |          | 101 | 13433 | 78 |    |     |
|  1 |  PARTITION RANGE ALL   |          | 101 | 13433 | 78 |  1 |   5 |
|  2 |   PARTITION HASH SINGLE|          | 101 | 13433 | 78 |  3 |   3 |
|* 3 |    TABLE ACCESS FULL   | EMP_COMP | 101 | 13433 | 78 |    |     |
------------------------------------------------------------------------

        在前面的示例中,谓词deptno=20支持对每个分区中的散列维度进行剪枝。因此,Oracle数据库只需要访问一个子分区。此子分区的数量在编译时已知道,因此不需要hash partition行源。

        最后看下面例子:

VARIABLE dno NUMBER; 

EXPLAIN PLAN FOR 
  SELECT * 
  FROM   emp_comp 
  WHERE  department_id = :dno; 


-----------------------------------------------------------------------
| Id| Operation              | Name    |Rows| Bytes |Cost|Pstart|Pstop|
-----------------------------------------------------------------------
| 0 | SELECT STATEMENT       |          | 101| 13433 | 78 |     |     |
| 1 |  PARTITION RANGE ALL   |          | 101| 13433 | 78 |   1 |   5 |
| 2 |   PARTITION HASH SINGLE|          | 101| 13433 | 78 | KEY | KEY |
|*3 |    TABLE ACCESS FULL   | EMP_COMP | 101| 13433 | 78 |     |     |
-----------------------------------------------------------------------

        最后两个示例是相同的,只是department_id =: dno替换了deptno=20。在最后一种情况下,子分区号在编译时是未知的,并且分配了一个哈希分区行源。对于这个行源,该选项是SINGLE,因为Oracle数据库只访问每个分区中的一个子分区。在步骤2中,PARTITION _ START和PARTITION _ STOP都被设置为KEY。这个值意味着Oracle数据库在运行时确定子分区的数量。

5.2.5.3,局部智能分区连接:examples

        在这些示例中,PQ_DISTRIBUTE提示显式地强制执行局部智能分区连接,因为查询优化器可以根据查询中的成本选择不同的计划。

Example 7-5    Partial Partition-Wise Join with Range Partition

CREATE TABLE dept2 AS SELECT * FROM departments;

ALTER TABLE dept2 PARALLEL 2;

CREATE TABLE emp_range_did PARTITION BY RANGE(department_id)
   (PARTITION emp_p1 VALUES LESS THAN (150),
    PARTITION emp_p5 VALUES LESS THAN (MAXVALUE) )
  AS SELECT * FROM employees;

ALTER TABLE emp_range_did PARALLEL 2;

EXPLAIN PLAN FOR 
  SELECT /*+ PQ_DISTRIBUTE(d NONE PARTITION) ORDERED */ e.last_name, 
         d.department_name 
  FROM   emp_range_did e, dept2 d 
  WHERE  e.department_id = d.department_id;

-----------------------------------------------------------------------------------------------
|Id| Operation                    |Name        |Row|Byte|Cost|Pstart|Pstop|TQ|IN-OUT|PQ Distrib|
-----------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT             |             |284 |16188|6 |   |   |       |     |          | 
| 1|  PX COORDINATOR              |             |    |     |  |   |   |       |     |          |
| 2|   PX SEND QC (RANDOM)        |:TQ10001     |284 |16188|6 |   |   | Q1,01 |P->S |QC (RAND) |
|*3|    HASH JOIN                 |             |284 |16188|6 |   |   | Q1,01 |PCWP |          |
| 4|     PX PARTITION RANGE ALL   |             |284 |7668 |2 | 1 | 2 | Q1,01 |PCWC |          |
| 5|      TABLE ACCESS FULL       |EMP_RANGE_DID|284 |7668 |2 | 1 | 2 | Q1,01 |PCWP |          |
| 6|     BUFFER SORT              |             |    |     |  |   |   | Q1,01 |PCWC |          |
| 7|      PX RECEIVE              |             | 21 | 630 |2 |   |   | Q1,01 |PCWP |          |
| 8|       PX SEND PARTITION (KEY)|:TQ10000     | 21 | 630 |2 |   |   |       |S->P |PART (KEY)|
| 9|        TABLE ACCESS FULL     |DEPT2        | 21 | 630 |2 |   |   |       |     |          |
-----------------------------------------------------------------------------------------------

5.2.5.4

5.2.5.5

5.2.5.5.1

5.2.5.5.2

5.2.5.5.3

5.2.5.6

5.2.6

5.3

5.3.1

5.3.2

5.3.3

你可能感兴趣的:(ORACLE_SQL,Tuning)