参考文档:《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组成的表层级结构:
在本例中,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