2.4 执行计划各个操作的含义
通常我们所说的执行计划操作包含两个部分:操作与其选项。例如,哈希关联反关联(HASH JOIN ANTI)中,哈希关联(HASH JOIN)是一种操作,“反”关联(ANTI)则是其选项;该操作还可以与其他选项(如“半”关联,SEMI)配合形成不同的执行计划操作。
执行计划中的操作数量非常多。我们下面列出的操作是Oracle 10gR2中的绝大多数操作。Oracle的每个版本都会有一些新的特性出现,而其中一些新特性又会带来新的操作,或者抛弃一些旧操作。如果发现执行计划出现新操作,读者可以结合相关新特性的描述来理解该操作的含义。
实际上,在执行计划里出现的操作包含两个信息,一个是操作类型(在PLAN_TABLE、V$SQL_PLAN等表或视图中,字段名为OPERATION);一个是操作的选项(在相关表和视图中,字段名为OPTIONS)。例如TABLE ACCESS BY INDEX ROWID,它的操作类型是TABLE ACCESS,即访问表,选项是BY INDEX ROWID,即通过索引中的ROWID来访问表。
提示:在11g中,可以通过固化表X$XPLTON和X$XPLTOO分别查询出所有操作类型和选项名称。一个操作可以有0到多个选项。但并不是所有选项都能用于所有的操作。
为了便于读者更好地理解这些操作,我们对这些操作类型进行了归类。但这个归类并非是一个绝对的划分,例如,某些操作可以划分到多个类别中(如MERGE JOIN PARTITION OUTER,既是一个数据关联操作,又是一个分区操作),但我们会按照操作的相关性将其划分在某个类别中。
2.4.1 语句类型
这些操作代表了这条语句的类型,在执行计划中,它们出现在ID为0的操作中。我们还可以将该类型操作分为数据定义语句(DDL)类型和数据管理语句(DML)类型,如表2-2所示。
CREATE TABLE STATEMENT执行创建表的语句,是最为常用的,其示例如下:
HELLODBA.COM>exec sql_explain('create table t_xpl(tid number, tname varchar2(20), status varchar2(2),
prop1 number, prop2 varchar2(30), prop3 varchar2(50)) tablespace demotxdata,'TYPICAL');
---------------------------------------
| Id | Operation | Name |
---------------------------------------
| 0 | CREATE TABLE STATEMENT | |
---------------------------------------
MERGE语句执行合并操作是比较难的,其示例如下:
HELLODBA.COM>exec sql_explain('merge into t_xpl t using (select object_id,object_name,owner from
t_objects) v on (t.tname=v.object_name) when matched then update set t.prop1=:1 when not matched
then insert (t.tid,t.tname,t.prop2) values (v.object_id,v.object_name,v.owner)','TYPICAL');
Plan hash value: 863798232
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 47585 | 11M| 46 (0)| 00:00:47 |
| 1 | MERGE | T_XPL | | | | |
| 2 | VIEW | | | | | |
| 3 | NESTED LOOPS OUTER | | 47585 | 11M| 46 (0)| 00:00:47 |
| 4 | TABLE ACCESS FULL | t_objects | 47585 | 1487K| 45 (0)| 00:00:46 |
| 5 | TABLE ACCESS BY INDEX ROWID| T_XPL | 1 | 226 | 1 (0)| 00:00:02 |
|* 6 | INDEX RANGE SCAN | T_XPL_IDX1 | 1 | | 1 (0)| 00:00:02 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access("T"."TNAME"(+)="OBJECT_NAME")
2.4.2 访问路径方法
对数据库对象的访问路径(Access Path)方法有很多,根据访问对象不同,可以将这类操作分为表访问操作、索引访问操作、固态表访问操作和物化视图访问操作。下面分别介绍这些操作,括号中内容为操作选项。
2.4.2.1 表访问操作
(1)TABLE ACCESS (FULL)
全表扫描,通过完全扫描的方式访问表。
(2)LOAD AS SELECT
以追加(APPEND)模式向表中插入数据。示例如下:
HELLODBA.COM>exec sql_explain('insert /*+ append */ into t_xpl(tid,tname) select object_id,object_name
from t_objects','TYPICAL');
Plan hash value: 1809206527
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 47585 | 1254K| 45 (0)| 00:00:46 |
| 1 | LOAD AS SELECT | T_XPL | | | | |
| 2 | TABLE ACCESS FULL| t_objects | 47585 | 1254K| 45 (0)| 00:00:46 |
------------------------------------------------------------------------------
(3)TABLE ACCESS (BY INDEX ROWID)
通过由索引中获取到的ROWID访问表。
(4)TABLE ACCESS BY LOCAL INDEX ROWID
通过由本地分区索引中获取到的ROWID访问表。
关键词释义
分区表:我们可以将表中的数据按照特定规则分开存储在不同的位置,这样的表即为分区表。每个分区都作为一个单独的段进行管理。在分区表上创建索引时,可以指定索引是否也按照相同规则分开存储。如果索引也分开存储,就是本地分区索引,否则为全局分区索引。
示例如下:
HELLODBA.COM>exec sql_explain('select * from t_objects_list where OBJECT_NAME=:A', 'TYPICAL');
Plan hash value: 3980774585
------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
Pstart| Pstop |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 88 | 3 (0)|
00:00:04 | | |
| 1 | PARTITION LIST ALL | | 1 | 88 | 3 (0)|
00:00:04 | 1 | 3 |
| 2 | TABLE ACCESS BY LOCAL INDEX ROWID| T_OBJECTS_LIST | 1 | 88 | 3 (0)|
00:00:04 | 1 | 3 |
|* 3 | INDEX RANGE SCAN | T_OBJECTS_LIST_IDX1 | 1 | | 2 (0)|
00:00:03 | 1 | 3 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("OBJECT_NAME"=:A)
(5)TABLE ACCESS (BY GLOBAL INDEX ROWID)
通过由全局分区索引中获取到的ROWID访问表。注意,全局分区索引并不一定建立在分区表上,也可以建立在非分区表上。
(6)TABLE ACCESS (BY USER ROWID)
通过用户输入或者从子查询中获取到的ROWID访问表。
(7)TABLE ACCESS (BY ROWID RANGE)
通过一段范围的多个ROWID来访问表。示例如下:
HELLODBA.COM>exec sql_explain('SELECT /*+ROWID(A)*/* FROM t_xpl A WHERE ROWID > :A', 'TYPICAL');
Plan hash value: 828298766
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 97 | 2 (0)| 00:00:03 |
|* 1 | TABLE ACCESS BY ROWID RANGE| T_XPL | 1 | 97 | 2 (0)| 00:00:03 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access(ROWID>CHARTOROWID(:A))
提示:我们可以通过SQL“提示”来强制语句执行计划中进行特定的操作。
(8)TABLE ACCESS (CLUSTER)
通过簇来访问表。
关键词释义
簇(Cluster):如果多个表可以共享一个或多个字段的数据,并且需要经常通过这些字段关联访问这些表时,我们可以将这些字段创建为一个簇,而这些表则可以围绕该簇创建为簇表。簇是一个单独的物理对象。
示例如下:
HELLODBA.COM>exec sql_explain('SELECT * FROM T_EEE WHERE A = :A', 'TYPICAL');
Plan hash value: 2109518915
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 201 | 1608 | 1 (0)| 00:00:02 |
| 1 | TABLE ACCESS CLUSTER| T_EEE | 201 | 1608 | 1 (0)| 00:00:02 |
|* 2 | INDEX UNIQUE SCAN | C_KEY2_IDX1 | 1 | | 1 (0)| 00:00:02 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("A"=TO_NUMBER(:A))
(9)TABLE ACCESS (HASH)
通过哈希簇来访问表。
提示:簇的存储结构有两种。一种是索引簇,即按照普通B*数的结构存储;另一种是哈希簇,即按照数据的哈希值进行存储。
(10)TABLE ACCESS (SAMPLE)
采样访问表,即以多数据块读取的方式扫描表的部分数据块。
某些时候,我们可能并不需要得到一个精确的结果,而是通过采样的方式访问表中的数据,然后按采样比例计算出大概结果。此时,我们就可以通过采样的方式访问表。
(11)TABLE ACCESS (SAMPLE BY ROWID RANGE)
通过对指定的一段范围的ROWID,以采样的方式访问表。示例如下:
HELLODBA.COM>exec sql_explain('SELECT * FROM T_XPL SAMPLE(5) WHERE rowid>:A', 'TYPICAL');
Plan hash value: 3098980765
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 97 | 2 (0)| 00:00:03 |
|* 1 | TABLE ACCESS SAMPLE BY ROWID RANGE| T_XPL | 1 | 97 | 2 (0)| 00:00:03 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access(ROWID>CHARTOROWID(:A))
2.4.2.2 索引访问操作
(1)INDEX (UNIQUE SCAN)
唯一索引扫描,即对唯一索引进行单一匹配访问。在唯一索引中,每一个非空键值只会存在一条。主键本身也是一个唯一索引。示例如下:
HELLODBA.COM>exec sql_explain('SELECT * FROM T_XPL WHERE tid=:A', 'TYPICAL');
Plan hash value: 3808404654
------------------------------------------------
| Id | Operation | Name |
------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS BY INDEX ROWID| T_XPL |
|* 2 | INDEX UNIQUE SCAN | T_XPL_PK |
------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("TID"=TO_NUMBER(:A))
(2)INDEX (RANGE SCAN)
索引范围扫描,即对(唯一或非唯一)索引进行范围匹配(>、<、>=、<=、like)访问,或者对非唯一索引进行单一匹配访问。
(3)INDEX (RANGE SCAN (MIN/MAX))
对索引进行范围扫描,以获取索引字段的最大值、最小值。
(4)INDEX (RANGE SCAN DESCENDING)
按照与索引逻辑顺序的相反顺序对索引进行范围扫描。
(5)INDEX (FAST FULL SCAN)
快速完全索引扫描,对索引进行快速完全扫描访问。这种访问方式中,不会按照索引的逻辑顺序访问,而是按照物理顺序读取所有的索引数据块,并且能够每次读取多个数据块。
提示:在B*Tree索引中,含有空键值的数据记录不会被构建到索引中。因此,如果索引字段允许为空的话,在查询索引字段数据时,如果未限制获取非空数据,则无法进行索引快速完全扫描。
(6)INDEX (SAMPLE FAST FULL SCAN)
索引快速完全采样扫描。与采样访问表类似,即以多数据块读取的方式扫描索引的部分数据块。示例如下:
HELLODBA.COM>exec sql_explain('SELECT /*+index_ffs(t_xpl t_xpl_idx1)*/tname FROM T_XPL SAMPLE(5) WHERE
tname is not null', 'TYPICAL');
Plan hash value: 1323535488
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 24 | 2 (0)| 00:00:03 |
|* 1 | INDEX SAMPLE FAST FULL SCAN| T_XPL_IDX1 | 1 | 24 | 2 (0)| 00:00:03 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("TNAME" IS NOT NULL)
(7)INDEX (FULL SCAN)
全索引扫描,即对索引进行完全扫描访问。这种访问方式与索引快速完全扫描的区别在于:
1)它是按照索引数据的逻辑顺序而不是物理存储顺序读取;
2)每次只能读取一个而不是多个数据块。
(8)INDEX (FULL SCAN (MIN/MAX))
对索引进行完全扫描访问,以获取索引字段的最大值、最小值。
(9)INDEX (FULL SCAN DESCENDING)
以索引逻辑顺序相反的顺序对索引进行完全扫描访问。
(10)INDEX (SKIP SCAN)
跳跃索引扫描,即对多字段复合索引扫描时,跳过索引中前导的一个或多个字段,而对后续字段进行匹配。
提示:在建立多字段复合索引时,Oracle会先按字段的顺序构建索引树,如果新数据记录的第一个字段值与索引中已有数据记录的第一个字段值相同,则会对第二个字段进行匹配来排序,以此类推。如果索引中的前导字段(可以是一个或者多个)的可区分数值非常少,即大多数数据记录拥有相同的数值,那么构建的索引树的逻辑结构,大多数都是由后续字段的顺序决定。而在这种情况下,如果一个查询条件中只含有后续字段的过滤条件,在对索引进行扫描时,可以将索引树视为多个小的索引树进行扫描,这种扫描方式就是跳跃索引扫描。
示例如下:
HELLODBA.COM>exec sql_explain('SELECT * FROM t_objects where object_name=:A','TYPICAL');
Plan hash value: 3426433533
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 234 | 15 (0)| 00:00:16 |
| 1 | TABLE ACCESS BY INDEX ROWID| T_OBJECTS | 2 | 234 | 15 (0)| 00:00:16 |
|* 2 | INDEX SKIP SCAN | T_OBJECTS_IDX1 | 2 | | 14 (0)| 00:00:14 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OBJECT_NAME"=:A)
filter("OBJECT_NAME"=:A)
(11)INDEX (SKIP SCAN DESCENDING)
以复合索引前导字段逻辑顺序的相反顺序进行跳跃索引扫描。
(12)DOMAIN INDEX
访问域索引。
关键词释义
域索引:在Oracle中,除了两种内建的数据结构(B*Tree和位图)外,用户还可以对索引结构进行扩展,建立其他结构的索引,例如四叉树,这需要创建、维护和访问索引的函数支持。由于这样的索引通常用于某个应用领域内,如全文检索。因此,这种索引称为域索引。
示例如下:
HELLODBA.COM>exec sql_explain('select table_name from t_tables where contains(table_name, ''T'') >0',
'TYPICAL');
Plan hash value: 1749265066
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 30 | 5 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T_TABLES | 1 | 30 | 5 (0)| 00:00:01 |
|* 2 | DOMAIN INDEX | T_TABLES_DIX01 | 2071 | | 4 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("CTXSYS"."CONTAINS"("TABLE_NAME",'T')>0)
(13)BITMAP INDEX (SINGLE VALUE)
位图索引单值范围,即对位图索引中的一个键值进行匹配访问。
关键词释义
位图索引:在Oracle中,存在两种数据结构的索引,一种是普通索引,以B*树结构构建索引,整个索引是树状结构,在叶子节点(即最底层节点)上存储索引记录,每条索引记录中包含了索引字段值和对应数据记录的ROWID,表中的每条数据记录(索引字段非空)在索引中都有一条索引记录,如果索引字段数值相同,则按ROWID顺序存储;另一种是位图索引,位图索引也是树状结构,但是它并不是对表中的每条数据记录都构建一条索引记录,而是将多个物理位置连续的数据记录映射到一条索引记录中,在叶子节点中,索引记录中除了索引字段值外,还包括其映射的多条表数据记录的起始地址和结束地址,最重要的是,它还包含一个位图,位图的每一个(bit)按序对应了一条表记录,位的值为1,说明表记录中的索引字段数值与索引记录中的数值相同,为0则表示表记录与索引记录的索引字段值不匹配。我们会在后面章节详细介绍位图索引的存储方式。
要注意的是,位图索引包含了索引字段为空的数据记录。
在Oracle(9i及以上版本)中,还有一种特殊的位图索引:位图关联索引(Bitmap Join Index)。创建这样的位图索引时,可以与其他表的相关字段进行关联。在对它们以索引字段作为过滤条件进行关联查询时,就可以避免对关联表的读取。创建了位图关联索引后,Oracle会在创建索引的表上建立一个隐藏的虚拟字段,用于优化器选择和产生对应的访问路径。
示例如下:
HELLODBA.COM>exec sql_explain('SELECT /*+index(t1 t_objects_idx3)*/count(1) FROM t_objects t1, t_users
t4 WHERE t1.owner = t4.username AND t1.status = :A','TYPICAL');
Plan hash value: 1812282537
------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 11 | 1 (0)| 00:00:02 |
| 1 | SORT AGGREGATE | | 1 | 11 | | |
|* 2 | TABLE ACCESS BY INDEX ROWID | T_OBJECTS | 15862 | 170K| 1 (0)| 00:00:02 |
| 3 | BITMAP CONVERSION TO ROWIDS| | | | | |
|* 4 | BITMAP INDEX SINGLE VALUE | T_OBJECTS_IDX3 | | | | |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("T1"."STATUS"=:A)
4 - access("T1"."SYS_NC00016$"=:A)
(14)BITMAP INDEX (RANGE SCAN)
位图索引范围扫描,即对位图索引中的多个键值进行匹配访问。
(15)BITMAP INDEX (FULL SCAN)
位图索引完全扫描,扫描方式与普通索引完全扫描类似。
(16)BITMAP INDEX (FAST FULL SCAN)
位图索引快速完全扫描,扫描方式与普通索引快速完全扫描类似。
2.4.2.3 固态表访问操作
(1)FIXED TABLE (FULL)
固态表完全扫描。
关键词释义
固态表是Oracle中的一种特殊表,以X$开头,属于SYS用户。它们存储的是Oracle实例内部使用的运行数据,如一些性能统计数据、latch信息等;在实例启动时加载到内存中,并在数据库的运行过程中动态添加删除,在实例关闭时被释放。系统中所有固态表可以由视图v$fixed_table查询得到。除SYS用户外,其他用户都不能直接查询固态表,只能通过视图查询。
(2)FIXED TABLE (FIXED INDEX)
访问固态表上的固态索引。示例如下:
HELLODBA.COM>explain plan for select * from X$KGLLK where KGLLKSQLID=:A;
Explained.
HELLODBA.COM>select * from table(dbms_xplan.display());
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------
Plan hash value: 235265292
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 568 | 0 (0)| 00:00:01 |
|* 1 | FIXED TABLE FIXED INDEX| X$KGLLK (ind:2) | 4 | 568 | 0 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("KGLLKSQLID"=:A)
(3)FAST DUAL
快速访问DUAL表。
DUAL表是Oracle中的一个特殊表,它只有一个字段、一条记录。有时候,我们需要通过SQL来获取一些特殊数据,如当前系统时间、一个表达式的结果等,可以通过SELECT FROM DUAL的方式获取。而实际上,我们不关心DUAL表本身的数据,只是通过它来构造出一条完整的语句。在FAST DUAL出现(10g)之前,Oracle需要实际读取一次DUAL表以完成一个语句的生命周期。引入FAST DUAL以后,则不需要去真正访问DUAL表,而是通过虚拟访问直接返回给上一层调用者。
注意,如果SELECT后的表达式中含有DUAL表的字段(dummy,或者*)时,还是会实际读取DUAL表的。
2.4.2.4 物化视图访问操作
(1)MAT_VIEW ACCESS (FULL)
物化视图(Materialized View)完全扫描。
物化视图是一个物理对象,其数据存储在相应的存储段(Segment)上。
(2)MAT_VIEW ACCESS (BY INDEX ROWID)
通过由索引中获取到的ROWID访问物化视图。
(3)MAT_VIEW ACCESS (BY USER ROWID)
通过用户输入或者从子查询中获取到的ROWID访问物化视图。
(4)MAT_VIEW ACCESS (BY ROWID RANGE)
通过一段范围的多个ROWID来访问物化视图
(5)MAT_VIEW ACCESS (SAMPLE)
采样访问物化视图,即以多数据块读取的方式扫描物化视图的部分数据块。
(6)MAT_VIEW ACCESS (SAMPLE BY ROWID RANGE)
通过对指定的一段范围的ROWID,以采样的方式访问物化视图。
(7)MAT_VIEW ACCESS (REWRITE ACCESS (FULL))
将查询重写后,完全扫描物化视图。示例如代码清单2-2所示。
提示:启用物化视图查询重写特性(物化视图本身要启用重写特性,并且系统或当前会话参数query_rewrite_enabled为true)后,如果通过物化视图访问能确保数据的完整性(检查规则由参数query_rewrite_integrity决定),则优化器会考虑查询重写,并访问相应物化视图。
(8)MAT_VIEW ACCESS (REWRITE ACCESS (BY INDEX ROWID))
在查询重写后,通过索引访问物化视图。
2.4.3 位图操作
该类操作基于位图数据(例如位图索引的索引记录)进行位操作,或者将其他数据结构与位图数据互换以利用位操作。
(1)BITMAP AND
对位图进行“与”(AND)操作。
位图索引中位图的每个位代表了其所对应的表记录中索引字段的值是否为索引记录中的值。如果有两个位图索引需要进行数值匹配过滤,则只需将表记录对应的位进行与操作就可以知道该记录是否满足条件。
(2)BITMAP OR
对位图进行“或”(OR)操作。在查询的过滤条件中,如果位图索引字段直接的关系是“或”,可以通过BITMAP OR来判断位图所映射的一批数据记录是否满足条件。
(3)BITMAP CONVERSION FROM ROWIDS
将一批数据记录的ROWID映射为位图。
对于普通B*树索引,Oracle也可以将数据记录的ROWID映射成一个位图,然后进行位图操作。进行这样的转换需要将系统参数_b_tree_bitmap_plans设置为TRUE。
(4)BITMAP CONVERSION TO ROWIDS
将位图映射为ROWID。在一个位图键值中,包含了一批数据记录的起始地址和结束地址,且这批记录是连续的,因此位图中的每一个位就按序对应了一条数据记录。示例如下:
HELLODBA.COM>exec sql_explain('select /*+index_combine(t t_tables_idx1 t_tables_idx3)*/* from t_tables
t where owner=:A and tablespace_name=:B','TYPICAL');
Plan hash value: 486431958
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 1836 | 3 (0)| 00:00:04 |
| 1 | TABLE ACCESS BY INDEX ROWID | T_TABLES | 9 | 1836 | 3 (0)| 00:00:04 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP AND | | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 5 | INDEX RANGE SCAN | T_TABLES_IDX1 | | | 1 (0)| 00:00:02 |
| 6 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 7 | INDEX RANGE SCAN | T_TABLES_IDX3 | | | 1 (0)| 00:00:02 |
--------------------------------------------------------------------------------------------------
(5)BITMAP CONVERSION COUNT
对位图进行计数操作。
(6)BITMAP MERGE
将多个位图合并成一个位图。
(7)BITMAP MINUS
对两个位图进行集合相减的操作。
一个位图映射了一批数据记录,在对多个位图索引中的一个或多个位图做排除过滤时,可以直接进行位图的相减操作。示例如下:
HELLODBA.COM>exec sql_explain('select count(1) from t_objects where status=:A and
owner!=:B','TYPICAL');
Plan hash value: 2648290687
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 2 (0)| 00:00:03 |
| 1 | SORT AGGREGATE | | 1 | 13 | | |
| 2 | BITMAP CONVERSION COUNT | | 22660 | 287K| 2 (0)| 00:00:03 |
| 3 | BITMAP MINUS | | | | | |
|* 4 | BITMAP INDEX SINGLE VALUE| T_OBJECTS_IDX2 | | | | |
|* 5 | BITMAP INDEX SINGLE VALUE| T_OBJECTS_IDX4 | | | | |
-----------------------------------------------------------------------------------------------
(8)BITMAP KEY ITERATION
对位图索引键值进行迭代。
在关系数据库中,可能会存在一些这样的数据:某个表存储大量的实际数据,而其中有多个字段和不同的表存在关联关系。在查询时,这张表需要与多个表进行关联,以获取关联数据或由关联表对数据进行过滤,这样的查询称为星形查询,存储实际基础数据表又称为事实表,关联表则称为维度表。例如,一个系统中的用户数据表往往是一个庞大的表,而用户有许多属性,如省份、所属单位、毕业学校等,会与其他多个表,如省份表、单位表、学校表等,发生关联。在这样一个环境中,事实表数据量相当庞大,而维度表的数据都比较少。按照星形查询的逻辑关系,即以事实表为中心,与多个维度关联数据进行过滤,在星形转换(Star Transformantion)的优化方式出现之前,需要以事实表为驱动表,再与多个维度进行关联查询,获取到数据之后,再对数据进行过滤,如果过滤的数据较多,这样查询过程的额外代价相当大。而在Oracle7i之后,如果事实表的这些关联字段上建立了位图索引后(或者启用了B*树位图转换特性后),优化器可以对这样的查询进行星形转换:即先由维度获取到过滤后的数据,由得到的关联字段中的数值对事实表的位图索引字段进行迭代,再将迭代到的位图进行合并,最后将位图转换为ROWID以获取事实表的数据。这样,就可以最大限度地减少读取事实表数据再进行过滤的额外开销。示例如代码清单2-3所示。
提示:星形转换只能在CBO模式下生效,且语句不能有绑定变量,优化器参数star_transformation_enabled必须为TRUE。
代码清单2-3 BITMAP KEY ITERATION示例
HELLODBA.COM>alter session set star_transformation_enabled=true;
session altered.
HELLODBA.COM>exec sql_explain('select /*+star_transformation*/* from t_constraints t3, t_users t4_1,
t_users t4_2 where t3.owner = t4_1.username and t3.r_owner = t4_2.username and t4_1.created =
to_date(''2011-01-01'',''YYYY-MM-DD'') and t4_2.created = to_date(''2006-01-01'',''YYYY-MM-
DD'')','TYPICAL');
Plan hash value: 511951138
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost(%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 292 | 160 (2)|00:02:41|
|* 1 | HASH JOIN | | 1 | 292 | 160 (2)|00:02:41|
|* 2 | HASH JOIN | | 10 | 2020 | 159 (2)|00:02:39|
| 3 | TABLE ACCESS BY INDEX ROWID | T_USERS | 1 | 90 | 1 (0)|00:00:02|
|* 4 | INDEX RANGE SCAN | T_USERS_IDX1 | 1 | | 1 (0)|00:00:02|
| 5 | TABLE ACCESS BY INDEX ROWID | T_CONSTRAINTS | 502 | 56224 | 157 (1)|00:02:38|
| 6 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 7 | BITMAP AND | | | | | |
| 8 | BITMAP MERGE | | | | | |
| 9 | BITMAP KEY ITERATION | | | | | |
| 10 | TABLE ACCESS BY INDEX ROWID | T_USERS | 1 | 90 | 1 (0)|00:00:02|
|* 11 | INDEX RANGE SCAN | T_USERS_IDX1 | 1 | | 1 (0)|00:00:02|
|* 12 | BITMAP INDEX RANGE SCAN | T_CONSTRAINTS_IDX1 | | | | |
| 13 | BITMAP MERGE | | | | | |
| 14 | BITMAP KEY ITERATION | | | | | |
| 15 | TABLE ACCESS BY INDEX ROWID | T_USERS | 1 | 90 | 1 (0)|00:00:02|
|* 16 | INDEX RANGE SCAN | T_USERS_IDX1 | 1 | | 1 (0)|00:00:02|
| 17 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 18 | INDEX RANGE SCAN | T_CONSTRAINTS_IDX2 | | | 1 (0)|00:00:02|
| 19 | TABLE ACCESS BY INDEX ROWID | T_USERS | 1 | 90 | 1 (0)|00:00:02|
|* 20 | INDEX RANGE SCAN | T_USERS_IDX1 | 1 | | 1 (0)|00:00:02|
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("T3"."R_OWNER"="T4_2"."USERNAME")
2 - access("T3"."OWNER"="T4_1"."USERNAME")
4 - access("T4_1"."CREATED"=TO_DATE('2011-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss'))
11 - access("T4_1"."CREATED"=TO_DATE('2011-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss'))
12 - access("T3"."OWNER"="T4_1"."USERNAME")
16 - access("T4_2"."CREATED"=TO_DATE('2006-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss'))
18 - access("T3"."R_OWNER"="T4_2"."USERNAME")
filter("T3"."R_OWNER" IS NOT NULL)
20 - access("T4_2"."CREATED"=TO_DATE('2006-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss'))
2.4.4 排序操作
以下操作与数据排序相关。
(1)BUFFER SORT
在内存中进行排序操作。有两点需要说明:
1)BUFFER SORT并不代表一定会进行排序操作。有时是Oracle为了借助私有内存的工作区来完成其他操作。
2)这里的内存不是共享内存(buffer cache),而是会话进程的私有内存(PGA)。
示例如下:
HELLODBA.COM>exec sql_explain('select * from t_tables t2, t_users t4 ','TYPICAL');
Plan hash value: 2197084780
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 84870 | 23M| 173 (0)| 00:02:54 |
| 1 | MERGE JOIN CARTESIAN| | 84870 | 23M| 173 (0)| 00:02:54 |
| 2 | TABLE ACCESS FULL | T_USERS | 41 | 3690| 2 (0)| 00:00:03 |
| 3 | BUFFER SORT | | 2070 | 412K| 171 (0)| 00:02:52 |
| 4 | TABLE ACCESS FULL | T_TABLES | 2070 | 412K| 4 (0)| 00:00:05 |
---------------------------------------------------------------------------------
(2)SORT AGGREGATE
通过对数据进行排序,以获取一个聚集结果。
注意,排序聚集操作并不一定意味着存在“排序”过程,例如COUNT()函数计数。并且,它也不要求在私有内存的排序工作区中完成。示例如下:
HELLODBA.COM>exec sql_explain('select /*+full(t)*/min(table_name) from t_tables t','TYPICAL');
Plan hash value: 97239127
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 18 | 6 (0)| 00:00:07 |
| 1 | SORT AGGREGATE | | 1 | 18 | | |
| 2 | TABLE ACCESS FULL| T_TABLES | 2070 | 37260 | 6 (0)| 00:00:07 |
-------------------------------------------------------------------------------
(3)SORT (CREATE INDEX)
通过对数据进行排序,以构建一个索引。
(4)SORT (GROUP BY)
通过对数据进行排序,以进行分组操作。
(5)SORT (GROUP BY ROLLUP)
通过对数据进行排序,以进行分组和合计操作。
(6)SORT (GROUP BY STOPKEY)
通过对数据进行排序,以进行分组操作,并且在达到终止条件(伪列ROWNUM不满足条件)时终止操作。
关键词释义
ROWNUM是一个伪列,即它的数据并非实际数据,而是在获取数据(Fetch)时产生的。它的数值代表了数据结果集中每一行数据的序号。
当ROWNUM和其他条件共同过滤查询时,获取到满足所有条件的限定数量的数据后,查询就结束了,不再读取和判断剩余数据。
(7)SORT (GROUP BY NOSORT)
无需排序(数据物理顺序与逻辑顺序一致),对数据进行分组,因而无需再进行排序操作。
通过索引完全扫描方式读取到的数据就是已经排序好的数据。
(8)SORT (GROUP BY NOSORT ROLLUP)
无需排序(数据物理顺序与逻辑顺序一致),对数据进行分组和合计,因而无需再进行排序操作。示例如下:
HELLODBA.COM>exec sql_explain('select /*+index(t t_tables_idx1)*/owner from t_tables t group by rollup
(owner)','TYPICAL');
Plan hash value: 2944151589
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 21 | 126 | 4 (0)| 00:00:04 |
| 1 | SORT GROUP BY NOSORT ROLLUP| | 21 | 126 | 4 (0)| 00:00:04 |
| 2 | INDEX FULL SCAN | T_TABLES_IDX1 | 2070 | 12420 | 4 (0)| 00:00:04 |
---------------------------------------------------------------------------------------------
(9)SORT (ORDER BY)
将数据排序,以实现数据的按序输出。
(10)SORT (ORDER BY STOPKEY)
将数据排序,以实现数据的按序输出,并且当满足排序个数(ROWNUM)时,停止排序。
按照某些排序方法(如选择排序)对一组数据进行排序时,会在每一轮中找到最小(或最大)数据;在对查询结果限制输出数量时,我们只需要找到n个最小(或最大)数据,无需再对剩余数据排序。
(11)SORT (UNIQUE)
对数据进行唯一排序,丢弃重复数据。
(12)SORT (UNIQUE NOSORT)
从已排序的数据中获取唯一数据,丢弃重复数据。示例如下:
HELLODBA.COM>exec sql_explain('select /*+index(t t_tables_IDX1)*/distinct owner from t_tables
t','TYPICAL');
Plan hash value: 3358567499
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 21 | 126 | 5 (20)| 00:00:05 |
| 1 | SORT UNIQUE NOSORT| | 21 | 126 | 5 (20)| 00:00:05 |
| 2 | INDEX FULL SCAN | T_TABLES_IDX1 | 2070 | 12420 | 4 (0)| 00:00:04 |
------------------------------------------------------------------------------------
(13)SORT (UNIQUE STOPKEY)
对数据进行唯一排序,丢弃重复数据,并且当满足排序个数(ROWNUM)时,停止排序。
(14)SORT PARTITION JOIN
这一操作出现在分区外关联过程中。它对数据进行排序,从而对数据进行分组(分区)。参见下节中的示例。
2.4.5 关联操作
以下操作为数据集关联(JOIN)的操作。
(1)NESTED LOOPS
通过嵌套循环获取关联数据。在进行嵌套循环关联时,第一个数据集是驱动数据集,即嵌套循环中的外循环。
(2)NESTED LOOPS (ANTI)
通过嵌套循环获取非关联数据。
在NESTED LOOPS (ANTI)的过程中,如果发现外循环中读取到的数据在内循环中能够匹配到,则立即终止内循环、丢弃该数据,开始下一轮循环。
(3)NESTED LOOPS (SEMI)
通过嵌套循环获取不完整关联数据。
在NESTED LOOPS (SEMI)的过程中,如果发现外循环中读取到的数据在内循环中能够匹配到,则立即终止内循环、返回该数据,开始下一轮循环。
(4)NESTED LOOPS (OUTER)
通过嵌套循环进行外关联,获取关联数据。
我们通常说的关联,如果没有特别指明,都是说内关联。而外关联与内关联的不同之处在于,无论在内循环中是否找到匹配数据,外循环中的数据都会被返回。
(5)NESTED LOOPS (PARTITION OUTER)
以左外关联的左边数据集(或右外关联的右边数据集)为外循环,将左外关联的右边数据集(或右外关联的左边数据集)分组(分区)进行外关联匹配。
提示:对于分区左(右)外关联,从逻辑上看,左(右)表需要与右(左)表中的数据分组(分区)分别做外关联。如果实际操作也按照这个逻辑实现,则意味着每次与一组数据进行关联,都要读取一次左(右)表数据。而在NESTED LOOPS PARTITION OUTER中,第一次读取左(右)表数据后,就被缓存在私有内存中,从而避免了多次重复读取共享内存数据。
示例如下:
HELLODBA.COM>exec sql_explain('select /*+use_nl(t_tables t4)*/owner, table_name, t4.username,
t4.created from t_tables partition by (owner) right outer join t_users t4 on
t_tables.owner=t4.username', 'TYPICAL');
Plan hash value: 3287690918
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4041 | 236K| 64 (2)| 00:01:05 |
| 1 | VIEW | | 4041 | 236K| 64 (2)| 00:01:05 |
| 2 | NESTED LOOPS PARTITION OUTER| | 4041 | 189K| 64 (2)| 00:01:05 |
| 3 | BUFFER SORT | | | | | |
| 4 | TABLE ACCESS FULL | T_USERS | 41 | 984 | 2 (0)| 00:00:03 |
| 5 | SORT PARTITION JOIN | | 99 | 2376 | 199 (2)| 00:03:20 |
|* 6 | INDEX FAST FULL SCAN | T_TABLES_PK | 99 | 2376 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - filter("T_TABLES"."OWNER"="T4"."USERNAME")
(6)HASH JOIN
通过数据集的哈希值匹配获取关联数据。
进行哈希关联时,先用哈希函数对左数据集(也称为构建数据集)按哈希值分区,并且创建哈希表,映射哈希键值与分区;然后逐条扫描右数据集(也称为探测数据集)的数据记录,用相同的哈希函数获取哈希值,并与哈希表进行匹配,找到相应的构建数据集的哈希分区,然后再用分区中的数据进行精确匹配。
哈希值与原始数据之间的关系是一对多的关系。即对于同一个哈希算法,一条原始数据只会有一个哈希值,且多条不同数据的哈希值可能相同。
(7)HASH JOIN (ANTI)
通过数据集的哈希值匹配获取非关联数据。
在匹配的过程中,如果哈希值相同、且数值匹配,则立即终止对关联哈希表中剩余哈希值的匹配、丢弃该哈希值,开始下一轮匹配。
提示:如果作为驱动的数据集的哈希表非常大,以至于内存中哈希空间无法一次性完成两边哈希表的匹配,则会将驱动哈希表分为一个小的哈希表,一次匹配一个,其他的则暂时存储到临时表空间中。我们可以注意到上述执行计划中有对临时表空间的估算值(TempSpc)。
(8)HASH JOIN (RIGHT ANTI)
取右边数据集做驱动,通过数据集的哈希值匹配获取非关联数据。
如果驱动数据集太大,以至于需要分配临时空间暂存哈希表,优化器则会考虑采用数据量较少的关联数据集作为驱动。此时,就需要用HASH JOIN(RIGHT ANTI)进行关联;同样,在匹配的过程中,如果两边的哈希值相同且数值匹配,则立即终止对关联哈希表中剩余哈希值的匹配、丢弃条数,开始下一轮匹配;不同的是,如果所有记录都未匹配,则返回关联数据集中的数据,而非驱动数据集的数据。
(9)HASH JOIN (ANTI SNA)
通过数据集的哈希值匹配获取非关联数据,并且同时关注是否有空值。这一操作在11g中引入。非关联查询时,是否进行检测控制,可以由优化器参数“_optimizer_null_aware_antijoin”控制。
示例(Oracle版本11.2.0.1)如下:
HELLODBA.COM>exec sql_explain('select /*+ordered*/object_name from t_objects o where object_name not in
(select /*+hash_aj*/table_name from t_tables t)', 'TYPICAL');
Plan hash value: 974496268
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 67645 | 3038K| | 427 (1)| 00:00:06 |
|* 1 | HASH JOIN ANTI SNA | | 67645 | 3038K| 2608K| 427 (1)| 00:00:06 |
| 2 | TABLE ACCESS FULL | T_OBJECTS | 72115 | 1760K| | 288 (1)| 00:00:04 |
| 3 | INDEX FAST FULL SCAN| T_TABLES_PK | 2696 | 56616 | | 6 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OBJECT_NAME"="TABLE_NAME")
(10)HASH JOIN (RIGHT ANTI SNA)
取右边数据集做驱动,通过数据集的哈希值匹配获取非关联数据,同时关注是否有空值。这一操作在11g中引入。
示例(Oracle版本11.2.0.1)如下:
HELLODBA.COM>exec sql_explain('select object_name from t_objects o where object_name not in (select
/*+qb_name(inv)*/table_name from t_tables t)', 'TYPICAL');
Plan hash value: 2913741112
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 67645 | 3038K| 295 (1)| 00:00:04 |
|* 1 | HASH JOIN RIGHT ANTI SNA| | 67645 | 3038K| 295 (1)| 00:00:04 |
| 2 | INDEX FAST FULL SCAN | T_TABLES_PK | 2696 | 56616 | 6 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | T_OBJECTS | 72115 | 1760K| 288 (1)| 00:00:04 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OBJECT_NAME"="TABLE_NAME")
(11)HASH JOIN (SEMI)
通过数据集的哈希值匹配获取不完整关联数据。
在匹配过程中,如果哈希值相同、且数值匹配,则立即终止对关联哈希表中剩余哈希值的匹配、返回该哈希值,开始下一轮匹配。
(12)HASH JOIN (RIGHT SEMI)
取右边数据集做驱动,通过数据集的哈希值匹配获取不完整关联数据。
如果驱动数据集太大,为了避免读写临时表空间,优化器会考虑通过HASH JOIN (RIGHT SEMI)进行关联;同样,在匹配过程中,如果哈希值相同且数值匹配,则立即终止对关联哈希表中剩余哈希值的匹配、返回关联数据集中的哈希值,开始下一轮匹配;返回关联数据集中的数据,而非驱动数据集的数据。
(13)HASH JOIN (OUTER)
通过数据集的哈希值匹配进行外(左)关联数据关联。
这里的外关联,实际上就是左外关联。通过哈希值匹配进行外关联操作时,左数据集的数据无论是否匹配到右数据集,都会被获取。
(14)HASH JOIN (RIGHT OUTER)
通过数据集的哈希值匹配进行右外关联数据关联。
(15)HASH JOIN (FULL OUTER)
通过数据集的哈希值匹配进行关联数据完全关联。
提示:该操作在10.2.0.4以后引入。
示例(Oracle版本10.2.0.4)如下:
HELLODBA.COM>exec sql_explain('select /*+NATIVE_FULL_OUTER_JOIN*/ts.tablespace_name, ts.block_size,
u.user_id from t_tablespaces ts full outer join t_users u on ts.tablespace_name=u.default_
tablespace','TYPICAL');
Plan hash value: 4264077763
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 49 | 2107 | 5 (20)| 00:00:05 |
| 1 | VIEW | VW_FOJ_0 | 49 | 2107 | 5 (20)| 00:00:05 |
|* 2 | HASH JOIN FULL OUTER| | 49 | 931 | 5 (20)| 00:00:05 |
| 3 | TABLE ACCESS FULL | T_TABLESPACES | 15 | 150 | 2 (0)| 00:00:03 |
| 4 | TABLE ACCESS FULL | T_USERS | 41 | 369 | 2 (0)| 00:00:03 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("TS"."TABLESPACE_NAME"="U"."DEFAULT_TABLESPACE")
(16)MERGE JOIN
通过合并已排序的数据进行关联。示例参见下例。
进行合并关联时,分别从两个数据集的首位开始对数据进行比较,如果相等,则说明数据匹配,则对两边数据集的下一条数据进行比较;如果不相等,则获取较小数值(如果为降序,则是大数值)所在数据集的下一条记录;当两边数据集都存在多条相等的记录时,需要在这多条记录之间进行多重投影匹配(Multiple Cast)。
(17)SORT (JOIN)
对数据进行排序,以执行合并关联(Merge Join)操作。
排序后的数据缓存在私有内存中,因而可以减少对共享缓存的访问次数和锁阀(Latch)的争用。
提示:在做合并关联时,要求两边数据集已经排好序。
(18)MERGE JOIN (ANTI)
通过进行数据合并关联获取非关联数据。
通过合并关联获取非关联数据时,如果左指针指向的数据大于右边数据,则右指针向后移一位,进行下一次比较;如果相等,两边指针都向后移一位;如果左边数据小于右边数据,则返回左数据(因为右边不会存在与其相等的数据了),并且指针向后移动。
由于右数据集只是起到数据比较的作用,而不需要返回数据,因此会对右数据集进行唯一性排序,排除重复数据,以减少匹配次数。
(19)MERGE JOIN (SEMI)
通过进行数据合并关联获取不完整关联数据。
MERGE JOIN(SEMI)与MERGE JOIN不同之处在于,由于右数据集不需要返回数据,且与左数据集的数据记录相等的数据只需要匹配一次,因此右数据集会进行唯一性排序,以排除重复数据。
(20)MERGE JOIN (OUTER)
通过进行数据合并关联而进行(左)外关联,获取关联数据。
通过合并关联进行左外关联时,当右边数据记录大于、等于左边数据记录时,都属于匹配记录。
(21)MERGE JOIN (PARTITION OUTER)
将右边数据集分组(分区),左数据集分别与每组数据进行合并外关联,以获取关联数据。
示例如下:
HELLODBA.COM>exec sql_explain('select /*+use_merge(t_tables t4)*/owner, table_name, t4.username,
t4.created from t_tables partition by (owner) rightouter join t_users t4 on
t_tables.owner=t4.username', 'TYPICAL');
Plan hash value: 2338076443
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4041 | 236K| | 39 (6)| 00:00:40 |
| 1 | VIEW | | 4041 | 236K| | 39 (6)| 00:00:40 |
| 2 | MERGE JOIN PARTITION OUTER| | 4041 | 189K| | 39 (6)| 00:00:40 |
| 3 | SORT JOIN | | 41 | 984 | | 3 (34)| 00:00:04 |
| 4 | TABLE ACCESS FULL | T_USERS | 41 | 984 | | 2 (0)| 00:00:03 |
|* 5 | SORT PARTITION JOIN | | 2070 | 49680 | 152K| 15 (7)| 00:00:16 |
| 6 | INDEX FAST FULL SCAN | T_TABLES_PK | 2070 | 49680 | | 2 (0)| 00:00:03 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("T_TABLES"."OWNER"="T4"."USERNAME")
filter("T_TABLES"."OWNER"="T4"."USERNAME")
(22)MERGE JOIN (CARTESIAN)
通过合并关联对数据集进行笛卡儿关联。
进行笛卡儿关联时,两边数据集中的任何一条数据记录都会与关联数据集中的任何一条记录匹配,换句话说,它们之间不存在关联条件,两边数据集的任何两条记录都满足笛卡儿的关联匹配要求。
(23)JOIN FILTER (CREATE)
创建一个过滤器,以用于布隆过滤器(Bloom Filter),示例参见下例。
(24)JOIN FILTER (USE)
使用系统创建的过滤器进行布隆过滤。
关键词释义
布隆过滤器(Bloom Filter)是用于判断一个元素是否属于一个数据集的数据结构。其基本思想就是用一个或多个哈希函数对数据集中的每个成员做映射,映射结果不是存在完整的哈希表中,而是一个位向量(bit vector)中。位向量所有位初始都为0,根据哈希结果将位向量中的相应位置1。对数据集中的所有成员的哈希计算完成后,就得到了该数据集的位向量。当需要判断一个元素是否属于该数据集时,也用相同的哈希函数对其映射得到它的位向量,然后将其位向量上所有为1的位与数据集位向量上相应位比较,如果发现数据集的位向量上某个位为0,可以判断这个元素不属于该数据集。而如果所有相应位都为1的话,那么该元素可能属于这个数据集。
下面介绍两个示例,如代码清单2-4和代码清单2-5 所示。
代码清单2-4 利用哈希关联的哈希算法建立布隆过滤器(需在11gR2中运行)
HELLODBA.COM>begin
2 sql_explain('
3 SELECT /*+ qb_name(m) place_group_by(@m (t@m)) */
4 owner, max(maxbytes)
5 FROM t_tables t, t_datafiles d
6 WHERE t.tablespace_name = d.tablespace_name
7 AND d.maxbytes BETWEEN 1000 AND 1000000
8 GROUP BY t.owner ',
9 'TYPICAL');
10 end;
11 /
Plan hash value: 2938435161
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 44 | 36 (9)| 00:00:01 |
| 1 | HASH GROUP BY | | 1 | 44 | 36 (9)| 00:00:01 |
|* 2 | HASH JOIN | | 1 | 44 | 35 (6)| 00:00:01 |
| 3 | JOIN FILTER CREATE | :BF0000 | 1 | 30 | 3 (0)| 00:00:01 |
|* 4 | TABLE ACCESS FULL | T_DATAFILES | 1 | 30 | 3 (0)| 00:00:01 |
| 5 | VIEW | VW_GBF_1 | 102 | 1428 | 31 (4)| 00:00:01 |
| 6 | HASH GROUP BY | | 102 | 1428 | 31 (4)| 00:00:01 |
| 7 | JOIN FILTER USE | :BF0000 | 2696 | 37744 | 30 (0)| 00:00:01 |
|* 8 | TABLE ACCESS FULL| T_TABLES | 2696 | 37744 | 30 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ITEM_1"="D"."TABLESPACE_NAME")
4 - filter("D"."MAXBYTES">=1000 AND "D"."MAXBYTES"<=1000000)
8 - filter(SYS_OP_BLOOM_FILTER(:BF0000,"T"."TABLESPACE_NAME"))
代码清单2-5 利用并行哈希关联的哈希算法建立布隆过滤器(需在11gR2中运行)
HELLODBA.COM>exec sql_explain('select /*+parallel(t 8) parallel(o 8) leading(t o) pq_distribute(o hash
hash)*/* from t_tables t, t_objects o where t.owner=o.owner and t.table_name=o.object_name and
o.status=:A','TYPICAl');
Plan hash value: 45711004
-------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 36058 | 11M| 45 (3)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 36058 | 11M| 45 (3)| 00:00:01 | Q1,02 | P->S | QC (RAND) |
|* 3 | HASH JOIN BUFFERED | | 36058 | 11M| 45 (3)| 00:00:01 | Q1,02 | PCWP | |
| 4 | JOIN FILTER CREATE | :BF0000 | 2696 | 634K| 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 5 | PX RECEIVE | | 2696 | 634K| 4 (0)| 00:00:01 | Q1,02 | PCWP | |
| 6 | PX SEND HASH | :TQ10000 | 2696 | 634K| 4 (0)| 00:00:01 | Q1,00 | P->P | HASH |
| 7 | PX BLOCK ITERATOR | | 2696 | 634K| 4 (0)| 00:00:01 | Q1,00 | PCWC | |
| 8 | TABLE ACCESS FULL| T_TABLES | 2696 | 634K| 4 (0)| 00:00:01 | Q1,00 | PCWP | |
| 9 | PX RECEIVE | | 36058 | 3486K| 40 (0)| 00:00:01 | Q1,02 | PCWP | |
| 10 | PX SEND HASH | :TQ10001 | 36058 | 3486K| 40 (0)| 00:00:01 | Q1,01 | P->P | HASH |
| 11 | JOIN FILTER USE | :BF0000 | 36058 | 3486K| 40 (0)| 00:00:01 | Q1,01 | PCWP | |
| 12 | PX BLOCK ITERATOR | | 36058 | 3486K| 40 (0)| 00:00:01 | Q1,01 | PCWC | |
|* 13 | TABLE ACCESS FULL| T_OBJECTS | 36058 | 3486K| 40 (0)| 00:00:01 | Q1,01 | PCWP | |
-------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("T"."OWNER"="O"."OWNER" AND "T"."TABLE_NAME"="O"."OBJECT_NAME")
13 - filter("O"."STATUS"=:A AND SYS_OP_BLOOM_FILTER(:BF0000,"O"."OWNER","O"."OBJECT_NAME"))
(25)PART JOIN FILTER (CREATE)
在哈希关联时创建布隆过滤,用于分区裁剪。该操作在11g被引入。
(26)PARTITION HASH (JOIN-FILTER)
利用布隆过滤,进行分区裁剪。示例如代码清单2-6所示。
代码清单2-6 PART JOIN FILTER (CREATE)和PARTITION HASH (JOIN-FILTER)示例(Oracle版本11.2.0.1)
HELLODBA.COM>exec sql_explain('select /*+full(t)*/* from t_tables_hash t, t_objects_hash o where
t.owner=o.owner and t.table_name=o.object_name and o.status=:A','TYPICAL');
Plan hash value: 2507685944
--------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 312 | 104K| 161 (2)| 00:00:02 | | |
|* 1 | HASH JOIN | | 312 | 104K| 161 (2)| 00:00:02 | | |
| 2 | PART JOIN FILTER CREATE | :BF0000 | 312 | 31200 | 127 (1)| 00:00:02 | | |
| 3 | PARTITION HASH SINGLE | | 312 | 31200 | 127 (1)| 00:00:02 | 1 | 1 |
|* 4 | TABLE ACCESS FULL | T_OBJECTS_HASH | 312 | 31200 | 127 (1)| 00:00:02 | 1 | 1 |
| 5 | PARTITION HASH JOIN-FILTER| | 2696 | 642K| 33 (0)| 00:00:01 |:BF0000|:BF0000|
| 6 | TABLE ACCESS FULL | T_TABLES_HASH | 2696 | 642K| 33 (0)| 00:00:01 |:BF0000|:BF0000|
--------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("T"."OWNER"="O"."OWNER" AND "T"."TABLE_NAME"="O"."OBJECT_NAME")
4 - filter("O"."STATUS"=:A)
2.4.6 层次查询操作
对具有层次关系的数据记录可用以下查询操作。
(1)CONNECT BY (WITH FILTERING)
在查询层次关系数据时,对父子关系数据进行连接。并且在连接数据时,对数据进行过滤。
这里的父子关系不是指关系表之间的联系,而是指数据行之间的层次关系,这个关系由一个或多个字段指明。
(2)CONNECT BY WITH (FILTERING(UNIQUE))
在查询树状关系数据时,对父子关系数据进行连接。并且在连接数据时,对数据进行过滤,并获得不重复数据。示例如下:
HELLODBA.COM>exec sql_explain('select distinct owner, table_name, level from t_constraints c start with
r_owner=:A connect by prior r_owner=owner and prior r_constraint_name = constraint_name','TYPICAL
OUTLINE');
Plan hash value: 363014889
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 150 | 18750 | 76 (4)|00:00:01|
| 1 | HASH UNIQUE | | 150 | 18750 | 76 (4)|00:00:01|
|* 2 | CONNECT BY WITH FILTERING (UNIQUE)| | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID | T_CONSTRAINTS | 75 | 3750 | 10 (0)|00:00:01|
|* 4 | INDEX RANGE SCAN | T_CONSTRAINTS_IDX2 | 75 | | 1 (0)|00:00:01|
|* 5 | HASH JOIN | | 75 | 9675 | 65 (4)|00:00:01|
| 6 | CONNECT BY PUMP | | | | | |
| 7 | TABLE ACCESS FULL | T_CONSTRAINTS | 10913 | 532K| 54 (2)|00:00:01|
-------------------------------------------------------------------------------------------------------
(3)CONNECT BY (WITHOUT FILTERING)
在查询层次关系数据时,对父子关系数据进行连接。在连接数据时,不对数据进行过滤。
(4)CONNECT BY WITHOUT FILTERING(UNIQUE)
在查询树状关系数据时,对父子关系数据进行连接。在连接数据时,不对数据进行过滤,并获得不重复数据。示例如下:
HELLODBA.COM>exec sql_explain('select distinct owner, table_name, level from t_constraints c connect
by prior r_owner=owner and prior r_constraint_name = constraint_name','TYPICAL OUTLINE');
Plan hash value: 1362935548
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name |Rows |Bytes|TempSpc|Cost (%CPU)|Time |
-------------------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT | |10913| 532K| | 188 (3)|00:00:02|
| 1| HASH UNIQUE | |10913| 532K| 696K| 188 (3)|00:00:02|
|* 2| CONNECT BY WITHOUT FILTERING (UNIQUE)| | | | | | |
| 3| TABLE ACCESS FULL | T_CONSTRAINTS |10913| 532K| | 54 (2)|00:00:01|
-------------------------------------------------------------------------------------------------------
(5)CONNECT BY (NO FILTERING WITH START-WITH)
在查询树状关系数据时,对父子关系数据进行连接。并结合START WITH的条件连接数据。
(6)CONNECT BY (NO FILTERING WITH SW (UNIQUE))
在查询树状关系数据时,对父子关系数据进行连接。结合START WITH的条件连接数据,并获得不重复数据。示例如下:
HELLODBA.COM>exec sql_explain('select /*+NO_CONNECT_BY_FILTERING(@"SEL$1")
CONNECT_BY_COMBINE_SW(@"SEL$1")*/distinct owner, table_name, level from t_constraints connect
by prior r_owner=owner and prior r_constraint_name = constraint_name start with
owner=:A','TYPICAL OUTLINE');
Plan hash value: 920710167
-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2055 | 250K| | 195 (3)| 00:00:02 |
| 1 | HASH UNIQUE | | 2055 | 250K| 1520K| 195 (3)| 00:00:02 |
|* 2 | CONNECT BY NO FILTERING WITH SW (UNIQUE)| | | | | | |
| 3 | TABLE ACCESS FULL | T_CONSTRAINTS | 10913 | 532K| | 54 (2)| 00:00:01 |
-------------------------------------------------------------------------------------------------------
(7)CONNECT BY PUMP
在查询层次关系数据时,由对数据泵读取的数据进行连接,建立数据的层次关系。
这个层次关系的建立是通过表的自我关联(Self Join)实现的。当树状查询存在过滤条件(START WITH)时,需要先访问表获取到过滤后的数据集,然后再遍历该数据集、进行自我关联建立层次关系。在遍历数据集时,为了避免再次访问表,Oracle创建了一个通道(即由数据泵连接)直接访问已经读取的数据。示例如代码清单2-7所示。
提示:是否启用该特性,可以通过参数_old_connect_by_enabled控制,false为启用,true为禁用。
代码清单2-7 CONNECT BY PUMP示例(Oracle版本11.2.0.1)
HELLODBA.COM>exec sql_explain('select owner, table_name, level from t_constraints connect by prior
r_owner=owner and prior r_constraint_name = constraint_name start with owner=:A','TYPICAL');
Plan hash value: 2575687216
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1336 | 228K| 171 (2)| 00:00:03 |
|* 1 | CONNECT BY WITH FILTERING| | | | | |
|* 2 | TABLE ACCESS FULL | T_CONSTRAINTS | 668 | 38744 | 56 (0)| 00:00:01 |
|* 3 | HASH JOIN | | 668 | 91516 | 113 (1)| 00:00:02 |
| 4 | CONNECT BY PUMP | | | | | |
| 5 | TABLE ACCESS FULL | T_CONSTRAINTS | 10694 | 605K| 56 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OWNER"=PRIOR "R_OWNER" AND "CONSTRAINT_NAME"=PRIOR "R_CONSTRAINT_NAME")
2 - filter("OWNER"=:A)
3 - access("connect$_by$_pump$_002"."prior r_owner"="OWNER" AND
"connect$_by$_pump$_002"."prior r_constraint_name "="CONSTRAINT_NAME")
2.4.7 视图操作
与视图、子查询相关的操作如下所示。
(1)VIEW
查询未与主查询合并的视图。
优化器在生产执行计划的过程中,查询转换器会尝试将子查询或视图的查询语句与主查询进行合并重写。但在某些情况下,如通过提示禁止合并、需要获取子查询的伪列ROWNUM数据等,则不会合并子查询或视图。
(2)VIEW PUSHED PREDICATE
查询未与主查询合并的视图,并将主查询中匹配条件推入视图查询语句中。示例如代码清单2-8所示。
代码清单2-8 VIEW PUSHED PREDICATE示例
HELLODBA.COM>exec sql_explain('select * from (select * from t_tables t2, t_users t4 where
t2.owner=t4.username order by table_name) v, t_objects t1 where v.owner(+)=t1.owner and
v.table_name(+)=t1.object_name and rownum<=10','TYPICAL');
Plan hash value: 1441381621
------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 27820 | 34 (0)| 00:00:35 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | NESTED LOOPS OUTER | | 10 | 27820 | 34 (0)| 00:00:35 |
| 3 | TABLE ACCESS FULL | T_OBJECTS | 10 | 1170 | 2 (0)| 00:00:03 |
| 4 | VIEW PUSHED PREDICATE | | 1 | 2665 | 3 (0)| 00:00:04 |
| 5 | SORT ORDER BY | | 1 | 294 | 3 (0)| 00:00:04 |
| 6 | NESTED LOOPS | | 1 | 294 | 3 (0)| 00:00:04 |
| 7 | TABLE ACCESS BY INDEX ROWID| T_TABLES | 1 | 204 | 1 (0)| 00:00:02 |
|* 8 | INDEX UNIQUE SCAN | T_TABLES_PK | 1 | | 1 (0)| 00:00:02 |
|* 9 | TABLE ACCESS FULL | T_USERS | 1 | 90 | 2 (0)| 00:00:03 |
------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=10)
8 - access("T2"."TABLE_NAME"="T1"."OBJECT_NAME" AND "T2"."OWNER"="T1"."OWNER")
9 - filter("T4"."USERNAME"="T1"."OWNER")
2.4.8 数据集合操作
下面是针对集合进行的操作。
(1)CONCATENATION
将两个或多个结果集进行拼接。
拼接实际上就是对多个集合的简单相加。为了消除被重复获取的数据,前一个集合的过滤条件会累加在后一个集合上。例如,在下例当中,第二个集合的操作上加上了过滤条件“LNNVL("TABLE_NAME"=:C)”;第三个集合的操作上加上了过滤条件“LNNVL("TABLE_NAME"=:C) AND LNNVL("OWNER"=:A)”。示例如代码清单2-9所示。
提示:Oracle中,表达式存在NULL数值,则表达式结果为NULL,因此进行数据匹配时,NULL数据仅在“IS NULL”表达式中为TRUE。而函数LNNVL确保NULL数据也被匹配。它的参数为一个表达式,当表达式结果为FASLE或者NULL时返回TRUE,否则返回FALSE。
代码清单2-9 CONCATENATION示例
HELLODBA.COM>exec sql_explain('select * from t_tables t where owner = :A or tablespace_name = :B or
table_name=:C','TYPICAL');
Plan hash value: 2954102479
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 275 | 56100 | 40 (0)| 00:00:01 |
| 1 | CONCATENATION | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| T_TABLES | 1 | 204 | 3 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | T_TABLES_PK | 1 | | 2 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID| T_TABLES | 99 | 20196 | 16 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | T_TABLES_IDX1 | 99 | | 1 (0)| 00:00:01 |
|* 6 | TABLE ACCESS BY INDEX ROWID| T_TABLES | 175 | 35700 | 21 (0)| 00:00:01 |
|* 7 | INDEX RANGE SCAN | T_TABLES_IDX3 | 184 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("TABLE_NAME"=:C)
4 - filter(LNNVL("TABLE_NAME"=:C))
5 - access("OWNER"=:A)
6 - filter(LNNVL("TABLE_NAME"=:C) AND LNNVL("OWNER"=:A))
7 - access("TABLESPACE_NAME"=:B)
(2)UNION-ALL
将两个或多个数据集进行联合(或者说数据集相加)。
UNION和UNION-ALL的不同之处在于,UNION会消除联合后的数据集中的重复数据,UNION-ALL则不会;UNION与CONCATENATION的不同之处在于,UNION是在数据集相加之后,利用其他操作消除重复值,而CONCATENATION则是在获取数据集时增加过滤条件消除重复值。
(3)UNION-ALL (PARTITION)
对分区视图中的分区结果集进行联合。
提示:分区视图是在Oracle 7i中引入的技术,将大表拆分成小表,创建视图,以获取更好的性能。在8i中,Oracle引入了分区表技术,在许多方面(如性能、可管理性)上超越了分区视图。
示例如下:
HELLODBA.COM>exec sql_explain('select /*+no_merge(v)*/* from v_tables v', 'TYPICAL');
Plan hash value: 4279065408
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 908 | 221K| 4 (0)| 00:00:05 |
| 1 | VIEW | V_TABLES | 908 | 221K| 4 (0)| 00:00:05 |
| 2 | UNION-ALL PARTITION| | | | | |
| 3 | TABLE ACCESS FULL | T_TABLES_SUB1 | 190 | 38000 | 2 (0)| 00:00:03 |
| 4 | TABLE ACCESS FULL | T_TABLES_SUB2 | 718 | 143K| 3 (0)| 00:00:04 |
--------------------------------------------------------------------------------------
PL/SQL procedure successfully completed.
(4)UNION ALL PUSHED PREDICATE
将查询谓词推入联合查询的子查询或视图中。示例如代码清单2-10所示。
提示:如果视图或子查询为“UNION ALL”,要实现该操作则需要确保优化器参数“_push_join_union_view”为TRUE(默认);如果视图或子查询为“UNION”,要实现该操作则需要确保优化器参数“_push_join_union_view2”为TRUE(默认)。
代码清单2-10 UNION ALL PUSHED PREDICATE示例
HELLODBA.COM>exec sql_explain('select /*+PUSH_PRED(v)*/* from (select /*+index(t2
t_tables_pk)*/t2.owner, t2.table_name from t_tables t2 union all select /*+index(t1
t_objects_pk)*/t1.owner, t1.object_name from t_objects t1) v, t_users t4 where v.owner=t4.
username and t4.user_id =:a','TYPICAL');
Plan hash value: 2016458074
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 497 | 64610 | 591 (0)| 00:09:52 |
| 1 | NESTED LOOPS | | 497 | 64610 | 591 (0)| 00:09:52 |
| 2 | TABLE ACCESS BY INDEX ROWID | T_USERS | 1 | 90 | 1 (0)| 00:00:02 |
|* 3 | INDEX UNIQUE SCAN | T_USERS_PK | 1 | | 1 (0)| 00:00:02 |
| 4 | VIEW | | 24 | 960 | 590 (0)| 00:09:51 |
| 5 | UNION ALL PUSHED PREDICATE | | | | | |
|* 6 | INDEX FULL SCAN | T_TABLES_PK | 99 | 2970 | 7 (0)| 00:00:07 |
|* 7 | TABLE ACCESS BY INDEX ROWID| T_OBJECTS | 2266 | 81576 | 583 (0)| 00:09:44 |
| 8 | INDEX FULL SCAN | T_OBJECTS_PK | 47585 | | 60 (0)| 00:01:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("T4"."USER_ID"=TO_NUMBER(:A))
6 - access("T2"."OWNER"="T4"."USERNAME")
filter("T2"."OWNER"="T4"."USERNAME")
7 - filter("T1"."OWNER"="T4"."USERNAME")
(5)INTERSECTION
取两个数据集的交集。
注意,交集中的数据不存在重复数据。
(6)MINUS
两个数据集相减。
注意,相减后的数据不存在重复数据。A数据集减去B数据集,相当于A数据集中去除了两个数据集的交集的数据以及重复数据。
(7)AND-EQUAL
对由两个或多个单字段索引获取ROWID数据集的交集,并消除重复的ROWID。示例如代码清单2-11所示。
代码清单2-11 AND-EQUAL 示例
HELLODBA.COM>exec sql_explain('select /*+AND_EQUAL(t T_TABLES_IDX1 T_TABLES_IDX3)*/* from t_tables
t where t.owner=:A and t.tablespace_name=:B', 'TYPICAL');
Plan hash value: 198370260
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 1674 | 7 (0)| 00:00:07 |
|* 1 | TABLE ACCESS BY INDEX ROWID| T_TABLES | 9 | 1674 | 7 (0)| 00:00:07 |
| 2 | AND-EQUAL | | | | | |
|* 3 | INDEX RANGE SCAN | T_TABLES_IDX1 | 99 | | 1 (0)| 00:00:02 |
|* 4 | INDEX RANGE SCAN | T_TABLES_IDX3 | 184 | | 1 (0)| 00:00:02 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("T"."OWNER"=:A AND "T"."TABLESPACE_NAME"=:B)
3 - access("T"."OWNER"=:A)
4 - access("T"."TABLESPACE_NAME"=:B)
2.4.9 分区操作
该分类中包含了所有与分区(Partition)相关的操作。注意,还有一些与分区相关的操作(例如,NESTED LOOPS (PARTITION OUTER))被归类到了其他分类。
(1)PARTITION LIST (ALL)
访问列举分区(LIST PARTITION)表的所有分区。
当查询的数据可能会出现在所有分区上时,需要列举所有分区。
(2)PARTITION LIST (SINGLE)
仅访问列举分区(LIST PARTITION)表的一个分区。
关键词释义
分区裁剪(PARTITION PRUNING):在对分区表进行查询时,优化器会检查谓词条件中是否存在对分区字段的过滤,如果存在,则可以仅访问符合条件的分区,即裁剪掉没必要访问的分区,从而提高效率。
示例如下:
HELLODBA.COM>exec sql_explain('select * from t_objects_list where owner = :A', 'TYPICAL');
Plan hash value: 1267596144
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
| 0 | SELECT STATEMENT | | 7871 | 676K| 9 (0)| 00:00:10 | | |
| 1 | PARTITION LIST SINGLE| | 7871 | 676K| 9 (0)| 00:00:10 | KEY | KEY|
| 2 | TABLE ACCESS FULL | T_OBJECTS_LIST | 7871 | 676K| 9 (0)| 00:00:10 | KEY | KEY|
(3)PARTITION LIST (INLIST)
访问列举分区(LIST PARTITION)表的所有与分区字段IN条件匹配的分区。
(4)PARTITION LIST (ITERATOR)
对列举分区(LIST PARTITION)表中符合分区字段范围匹配条件的分区进行迭代。
(5)PARTITION LIST (OR)
访问列举分区(LIST PARTITION)中符合两个或多个分区字段匹配条件之一的分区。
(6)PARTITION LIST (SUBQUERY)
访问列举分区(LIST PARTITION)表中所有与子查询条件匹配的分区。示例如代码清单2-12所示。
代码清单2-12 PARTITION LIST (SUBQUERY)示例
HELLODBA.COM>exec sql_explain('select /+use_hash(tp) X_DYN_PRUNE/ tp. from t_objects_list tp where
exists (select username from t_users t4 where tp.owner=t4.username and t4.user_id = :A)', 'TYPICAL');
Plan hash value: 1477934472
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
| 0 | SELECT STATEMENT | | 7871 | 822K| 26 (4)| 00:00:26 | | |
|* 1 | HASH JOIN | | 7871 | 822K| 26 (4)| 00:00:26 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| T_USERS | 1 | 19 | 1 (0)| 00:00:02 | | |
|* 3 | INDEX UNIQUE SCAN | T_USERS_PK | 1 | | 1 (0)| 00:00:02 | | |
| 4 | PARTITION LIST SUBQUERY | | 23614 | 2029K| 24 (0)| 00:00:25 |KEY(SQ)|KEY(SQ)|
| 5 | TABLE ACCESS FULL | T_OBJECTS_LIST | 23614 | 2029K| 24 (0)| 00:00:25 |KEY(SQ)|KEY(SQ)|
Predicate Information (identified by operation id):
1 - access("TP"."OWNER"="T4"."USERNAME")
3 - access("T4"."USER_ID"=TO_NUMBER(:A))
(7)PARTITION RANGE (ALL)
访问范围分区(RANGE PARTITION)表的所有分区。
(8)PARTITION RANGE (SINGLE)
仅访问范围分区(RANGE PARTITION)表的一个分区。
(9)PARTITION RANGE (INLIST)
访问范围分区(RANGE PARTITION)表的所有与分区字段IN条件匹配的分区。
(10)PARTITION RANGE (ITERATOR)
对范围分区(RANGE PARTITION)表中符合分区字段匹配条件的分区进行迭代。
(11)PARTITION RANGE (OR)
访问范围分区(RANGE PARTITION)表中符合两个或多个分区字段匹配条件之一的分区。
(12)PARTITION RANGE (SUBQUERY)
访问范围分区(RANGE PARTITION)表的所有与子查询条件相匹配的分区。
(13)PARTITION RANGE (MULTI-COLUMN)
访问以组合字段为分区键的范围分区(RANGE PARTITION)表中所有与多个分区字段过滤条件相匹配的分区。示例如下:
HELLODBA.COM>exec sql_explain('select tr.* from t_objects_range tr where tr.owner<:A and
tr.object_name<:B', 'TYPICAL');
Plan hash value: 1725627318
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
| 0 | SELECT STATEMENT | | 59 | 5782 | 26 (0)| 00:00:27 | | |
| 1 | PARTITION RANGE MULTI-COLUMN| | 59 | 5782 | 26 (0)| 00:00:27 |KEY(MC)|KEY(MC)|
|* 2 | TABLE ACCESS FULL | T_OBJECTS_RANGE | 59 | 5782 | 26 (0)| 00:00:27 |KEY(MC)|KEY(MC)|
Predicate Information (identified by operation id):
2 - filter("TR"."OWNER"<:A AND "TR"."OBJECT_NAME"<:B)
(14)PARTITION HASH (ALL)
访问哈希分区(HASH PARTITION)表的所有分区。
(15)PARTITION HASH (SINGLE)
仅访问哈希分区(HASH PARTITION)表的一个分区。
(16)PARTITION HASH (INLIST)
访问哈希分区(HASH PARTITION)表的所有与分区字段IN条件相匹配的分区。
(17)PARTITION HASH (ITERATOR)
对哈希分区(HASH PARTITION)表中符合分区字段匹配条件的分区进行迭代。示例如代码清单2-13所示。
提示:仅当访问有限个分区时,优化器会考虑对分区进行迭代。由于哈希算法的特性,即哈希值的均匀分布性,对分区字段的范围匹配可能会访问到所有分区,因此这种情况下优化器不会对分区迭代。
代码清单2-13 PARTITION HASH (ITERATOR)示例
HELLODBA.COM>exec sql_explain('select th.* from t_objects_hash th where owner in (select username from
t_users where rownum<=3)', 'TYPICAL');
Plan hash value: 4248403627
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
| 0 | SELECT STATEMENT | | 576 | 66240 | 7 (15)| 00:00:08 | | |
| 1 | NESTED LOOPS | | 576 | 66240 | 7 (15)| 00:00:08 | | |
| 2 | VIEW | VW_NSO_1 | 3 | 51 | 2 (0)| 00:00:03 | | |
| 3 | HASH UNIQUE | | 1 | 51 | | | | |
|* 4 | COUNT STOPKEY | | | | | | | |
| 5 | TABLE ACCESS FULL | T_USERS | 41 | 697 | 2 (0)| 00:00:03 | | |
| 6 | PARTITION HASH ITERATOR| | 576 | 56448 | 4 (0)| 00:00:05 | KEY | KEY |
|* 7 | TABLE ACCESS FULL | T_OBJECTS_HASH | 576 | 56448 | 4 (0)| 00:00:05 | KEY | KEY |
Predicate Information (identified by operation id):
4 - filter(ROWNUM<=3)
7 - filter("OWNER"="$nso_col_1")
2.4.10 并行查询操作
下面介绍与并行查询相关的操作。
关键词释义
并行查询:并行查询是通过将查询操作任务分成若干个子任务,交由多个并行服务进程(或线程)执行,以提高处理速度的方法。
并行度(Degree Of Parallelism,DOP):与单个操作关联的并行服务进程数即为并行度。并行度可以通过创建对象的DDL语句指定,也可以通过语句中的提示指定。
通常,采用并行查询的目的是减少查询响应时间,而不是以降低传统性能指标(如IO、CPU)为度量标准(相反,这些数据可能会比串行执行情况下更高)。
提示:参数parallel_max_servers控制最大并行服务进程数,parallel_min_servers控制最小并行服务进程数,parallel_max_servers为1则禁用并行查询。
(1)PX COORDINATOR
并行执行协调者,也为查询协调者(Query Coordinator,QC)。在并行查询语句的执行计划中,PX COORDINATOR是执行并行操作的第一步。
在并行执行过程中,并行执行协调者是一个独立的会话(即用户会话本身),负责并行语句的初始化,并且将可并行操作分解,按照一定策略将分解后的子任务分发给并行服务会话。此外,它还承担一部分无法并行操作的任务。
(2)PX SEND QC (RANDOM)
并行服务进程通过表队列(Table Queue)将数据随机发送给协调者。
关键词释义
表队列(Table Queue,TQ):表队列是协调者与并行服务进程之间、并行进程相互之间的数据传输通道。执行计划中的TQ字段显示了当前操作中进程之间通信所用的表队列信息。
示例如代码清单2-14所示。
代码清单2-14 PX COORDINATOR和PX SEND QC示例
HELLODBA.COM>alter system set parallel_max_servers=20 scope=memory;
System altered.
HELLODBA.COM>exec sql_explain('select /+parallel(t 2)/* from t_objects t', 'TYPICAL');
Plan hash value: 3674079550
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
| 0 | SELECT STATEMENT | | 47585 | 5436K| 30 (0)| 00:00:31 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM)| :TQ10000 | 47585 | 5436K| 30 (0)| 00:00:31 | Q1,00 | P->S | QC (RAND) |
| 3 | PX BLOCK ITERATOR | | 47585 | 5436K| 30 (0)| 00:00:31 | Q1,00 | PCWC | |
| 4 | TABLE ACCESS FULL| T_OBJECTS | 47585 | 5436K| 30 (0)| 00:00:31 | Q1,00 | PCWP | |
(3)PX SEND QC (ORDER)
并行服务进程通过表队列(Table Queue)将数据按序发送给协调者。
(4)PX RECEIVE
消费者(Consumer)通过表队列(Table Queue)接收从生产者(Producer)发送过来的数据。
关键词释义
生产者(Producer)/消费者(Consumer)模式:为了提高数据处理效率,并行进程被分成进程集,成对协同工作:一组负责“生产”数据行,称为“生产者”(Producer);另一组则“消费”这些数据行,称为“消费者”。例如,在进行关联(JOIN)查询时,一组进程集从一张表中读取数据,并通过表队列(Table Queue)通道发送数据,这组进程集就是生产者;另外一组进程集则从队列通道中接收数据,并将数据与从另外一张表的数据进行关联,这组进程集就是消费者。
在生产者/消费者模式下,实际需要的并行服务进程数量是指定并行度(DOP)的两倍。除了对单个表的简单查询外,大多数并行执行语句都会运行在生产者/消费者模式下。
(5)PX SEND (RANGE)
生产者(Producer)依照数据范围将数据分发给不同消费者(Consumer)。
关键词释义
数据分发(Distribution):在并行查询中,每一个生产者进程都只会获取相互之间不重叠的一部分数据,而消费者在执行某些操作(如关联)时,需要从多个生产者获取数据,生产者就需要按照一定方式将数据分发给需要其数据的消费者。执行计划中,PQ Distrib字段表示分发方式。
在并行查询中,同一个操作可能会有多个进程同时进行,因此,在执行计划中需要描述父子操作之间关系,字段IN-OUT就是显示该信息。并行操作之间关系包括:
并行至串行(Parallel to Serial,P->S):多个进程同时执行的并行操作向单个进程执行的串行操作发送数据,其数据的发送、接收需要通过表队列(Table Queue)完成。如并行服务进程向协调者发送数据。
并行至并行(Parallel to Parallel,P->P):多个进程同时执行的并行操作向多个进程同时执行的并行操作发送数据,其数据的发送、接收需要通过表队列(Table Queue)完成。如多个生产者进程向多个消费者进程分发数据。
串行至并行(Serial to Parallel,S->P):单个进程执行的串行操作向多个进程同时执行的并行操作发送数据,其数据的发送、接收需要通过表队列(Table Queue)完成。如某些情况下,并行进程的并发操作的子操作无法并行执行,或者子操作的对象太小,并行化代价大于串行代价。
与父操作捆绑进行的并行操作(Parallel Combined With Parent,PCWP):父子操作都是并行操作,但必须由同一进程完成。
与子操作捆绑进行的并行操作(Parallel Combined With Child,PCWC):父子操作都是并行操作,但必须由同一进程完成。
示例如代码清单2-15所示。
代码清单2-15 PX SEND QC (ORDER)、PX RECEIVE和PX SEND (RANGE)示例
HELLODBA.COM>exec sql_explain('select /+parallel(t 2)/* from t_tables t order by table_name',
'TYPICAL');
Plan hash value: 4019464745
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
| 0 | SELECT STATEMENT | | 2070 | 412K| | 47 (3)| 00:00:47 | | | |
| 1 | PX COORDINATOR | | | | | | | | | |
| 2 | PX SEND QC (ORDER) | :TQ10001 | 2070 | 412K| | 47 (3)| 00:00:47 | Q1,01 | P->S | QC (ORDER) |
| 3 | SORT ORDER BY | | 2070 | 412K| 1288K| 47 (3)| 00:00:47 | Q1,01 | PCWP | |
| 4 | PX RECEIVE | | 2070 | 412K| | 3 (0)| 00:00:04 | Q1,01 | PCWP | |
| 5 | PX SEND RANGE | :TQ10000 | 2070 | 412K| | 3 (0)| 00:00:04 | Q1,00 | P->P | RANGE |
| 6 | PX BLOCK ITERATOR | | 2070 | 412K| | 3 (0)| 00:00:04 | Q1,00 | PCWC | |
| 7 | TABLE ACCESS FULL| T_TABLES | 2070 | 412K| | 3 (0)| 00:00:04 | Q1,00 | PCWP | |
(6)PX SEND (BROADCAST)
生产者(Producer)将数据广播分发给所有消费者(Consumer)。
(7)PX SEND (BROADCAST LOCAL)
生产者(Producer)将数据广播分发给所有操作于同一分区的消费者(Consumer)。
(8)PX SEND (HASH)
生产者(Producer)依照哈希值将数据分发给相应的消费者(Consumer)。
(9)PX SEND (HASH (BLOCK ADDRESS))
生产者(Producer)依照哈希值(由数据块地址而不是数据记录中的字段数值计算得出)将数据分发给相应的消费者(Consumer)。示例如代码清单2-16所示。
提示:如果要使DML语句并行化,需要激活当前会话的并行DML属性。
代码清单2-16 PX SEND (HASH (BLOCK ADDRESS))示例
HELLODBA.COM>alter session enable parallel dml;
Session altered.
HELLODBA.COM>exec sql_explain('delete /+parallel(o)/from t_objects o where exists (select 1 from
t_tables t where t.owner=o.owner and t.table_name = o.object_name)','TYPICAL');
Plan hash value: 948418918
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
| 0 | DELETE STATEMENT | | 23 | 2300 | 15 (0)| 00:00:16 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 23 | 2300 | 15 (0)| 00:00:16 | Q1,02 | P->S | QC (RAND) |
| 3 | INDEX MAINTENANCE | T_OBJECTS | | | | | Q1,02 | PCWP | |
| 4 | PX RECEIVE | | 23 | 2300 | 15 (0)| 00:00:16 | Q1,02 | PCWP | |
| 5 | PX SEND RANGE | :TQ10001 | 23 | 2300 | 15 (0)| 00:00:16 | Q1,01 | P->P | RANGE |
| 6 | DELETE | T_OBJECTS | | | | | Q1,01 | PCWP | |
| 7 | PX RECEIVE | | 23 | 2300 | 15 (0)| 00:00:16 | Q1,01 | PCWP | |
| 8 | PX SEND HASH (BLOCK ADDRESS)| :TQ10000 | 23 | 2300 | 15 (0)| 00:00:16 | Q1,00 | P->P | HASH (BLOCK|
| 9 | NESTED LOOPS SEMI | | 23 | 2300 | 15 (0)| 00:00:16 | Q1,00 | PCWP | |
| 10 | PX BLOCK ITERATOR | | | | | | Q1,00 | PCWC | |
| 11 | TABLE ACCESS FULL | T_OBJECTS | 47585 | 3531K| 15 (0)| 00:00:16 | Q1,00 | PCWP | |
|* 12 | INDEX UNIQUE SCAN | T_TABLES_PK | 1 | 24 | 1 (0)| 00:00:02 | Q1,00 | PCWP | |
Predicate Information (identified by operation id):
12 - access("T"."TABLE_NAME"="O"."OBJECT_NAME" AND "T"."OWNER"="O"."OWNER")
(10)PX SEND (HYBRID (ROWID PKEY))
生产者(Producer)依照ROWID和主键将数据分发给相应的消费者(Consumer)。示例如代码清单2-17所示。
代码清单2-17 PX SEND (HYBRID (ROWID PKEY))示例
HELLODBA.COM>alter session enable parallel dml;
Session altered.
HELLODBA.COM>exec sql_explain('merge /+parallel(t 6) full(t)/ into t_xpl t using (select
object_id,object_name,owner from t_objects o) v on (t.tname=v.object_name) when matched then update
set t.prop1=:1 when not matched then insert (t.tid,t.tname,t.prop2) values (v.object_id,v.object_
name,v.owner)','TYPICAL');
Plan hash value: 2874460846
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
| 0 | MERGE STATEMENT | | 47585 | 11M| 57 (2)| 00:00:57 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10004 | 47585 | 11M| 57 (2)| 00:00:57 | Q1,04 | P->S | QC (RAND) |
| 3 | INDEX MAINTENANCE | T_XPL | | | | | Q1,04 | PCWP | |
| 4 | PX RECEIVE | | 47585 | 11M| 57 (2)| 00:00:57 | Q1,04 | PCWP | |
| 5 | PX SEND RANGE | :TQ10003 | 47585 | 11M| 57 (2)| 00:00:57 | Q1,03 | P->P | RANGE |
| 6 | MERGE | T_XPL | | | | | Q1,03 | PCWP | |
| 7 | PX RECEIVE | | 47585 | 11M| 57 (2)| 00:00:57 | Q1,03 | PCWP | |
| 8 | PX SEND HYBRID (ROWID PKEY)| :TQ10002 | 47585 | 11M| 57 (2)| 00:00:57 | Q1,02 | P->P | HYBRID(ROW|
| 9 | VIEW | | | | | | Q1,02 | PCWP | |
|* 10 | HASH JOIN RIGHT OUTER | | 47585 | 11M| 57 (2)| 00:00:57 | Q1,02 | PCWP | |
| 11 | PX RECEIVE | | 1 | 226 | 2 (0)| 00:00:03 | Q1,02 | PCWP | |
| 12 | PX SEND HASH | :TQ10001 | 1 | 226 | 2 (0)| 00:00:03 | Q1,01 | P->P | HASH |
| 13 | PX BLOCK ITERATOR | | 1 | 226 | 2 (0)| 00:00:03 | Q1,01 | PCWC | |
| 14 | TABLE ACCESS FULL | T_XPL | 1 | 226 | 2 (0)| 00:00:03 | Q1,01 | PCWP | |
| 15 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 16 | PX RECEIVE | | 47585 | 1626K| 54 (0)| 00:00:55 | Q1,02 | PCWP | |
| 17 | PX SEND HASH | :TQ10000 | 47585 | 1626K| 54 (0)| 00:00:55 | | S->P | HASH |
| 18 | TABLE ACCESS FULL | T_OBJECTS | 47585 | 1626K| 54 (0)| 00:00:55 | | | |
Predicate Information (identified by operation id):
10 - access("T"."TNAME"(+)="OBJECT_NAME")
(11)PX SEND (PARTITION (KEY))
以分区为划分粒度,生产者(Producer)依照分区键值将数据发送给相应的消费者(Consumer)。示例如代码清单2-18所示。
关键词释义
并行颗粒(Granule):并行处理中的最小工作单位。Oracle将可并行操作(如表扫描)划分为若干个颗粒,并行服务进程每次执行一个颗粒的操作。包含数据块范围(Block Range)颗粒和分区(Partition)颗粒:
数据块范围(Block Range)颗粒:数据块范围颗粒是并行操作中的最基本颗粒。并行服务进程每次读取或操作一段连续的数据块。
分区(Partition)颗粒: 每个并行服务进程操作一个分区或子分区。因此,这种颗粒划分情况下,分区数量决定了最大并行度。通常在对本地分区索引进行范围扫描或者分区表之间进行关联时可能会以分区为粒度。
代码清单2-18 PX SEND (PARTITION (KEY))示例
HELLODBA.COM>exec sql_explain('select /+parallel(t 4) parallel(o 4) pq_distribute(t none partition)/*
from t_objects_list o, t_tables_list t where o.owner=t.owner', 'TYPICAL');
Plan hash value: 3990730760
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |
| 0 | SELECT STATEMENT | | 7808K| 2204M| 9 (0)| 00:00:10 | | | | | |
| 1 | PX COORDINATOR | | | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10001 | 7808K| 2204M| 9 (0)| 00:00:10 | | | Q1,01 | P->S | QC (RAND) |
|* 3 | HASH JOIN BUFFERED | | 7808K| 2204M| 9 (0)| 00:00:10 | | | Q1,01 | PCWP | |
| 4 | PX PARTITION LIST ALL | | 992 | 201K| 2 (0)| 00:00:03 | 1 | 3 | Q1,01 | PCWC | |
| 5 | TABLE ACCESS FULL | T_TABLES_LIST | 992 | 201K| 2 (0)| 00:00:03 | 1 | 3 | Q1,01 | PCWP | |
| 6 | PX RECEIVE | | 23614 | 2029K| 7 (0)| 00:00:07 | | | Q1,01 | PCWP | |
| 7 | PX SEND PARTITION (KEY)| :TQ10000 | 23614 | 2029K| 7 (0)| 00:00:07 | | | Q1,00 | P->P | PART (KEY) |
| 8 | PX BLOCK ITERATOR | | 23614 | 2029K| 7 (0)| 00:00:07 | 1 | 3 | Q1,00 | PCWC | |
| 9 | TABLE ACCESS FULL | T_OBJECTS_LIST | 23614 | 2029K| 7 (0)| 00:00:07 | 1 | 3 | Q1,00 | PCWP | |
Predicate Information (identified by operation id):
3 - access("O"."OWNER"="T"."OWNER")
(12)PX SEND (ROUND-ROBIN)
串行进程以轮询方式将数据分发给并行服务进程。示例如代码清单2-19所示。
关键词释义
轮询(Round Robin):轮询是一种最简单的资源选取方式。每一次从一组资源队列中选取一个,且为上一次所选取资源的下一个资源,如果选取到了队列的末尾,则从第一个继续轮询。
代码清单2-19 PX SEND (ROUND-ROBIN)示例
HELLODBA.COM>alter session enable parallel dml;
Session altered.
HELLODBA.COM>exec sql_explain('insert /+parallel(t_xpl)/into t_xpl(tid) select user_id from
t_users','TYPICAL');
Plan hash value: 3327021059
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT|PQ Distrib |
| 0 | INSERT STATEMENT | | 41 | 82 | 1 (0)| 00:00:02 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10001 | 41 | 82 | 1 (0)| 00:00:02 | Q1,01 | P->S | QC (RAND) |
| 3 | LOAD AS SELECT | T_XPL | | | | | Q1,01 | PCWP | |
| 4 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 5 | PX RECEIVE | | 41 | 82 | 1 (0)| 00:00:02 | Q1,01 | PCWP | |
| 6 | PX SEND ROUND-ROBIN| :TQ10000 | 41 | 82 | 1 (0)| 00:00:02 | | S->P | RND-ROBIN |
| 7 | INDEX FULL SCAN | T_USERS_PK | 41 | 82 | 1 (0)| 00:00:02 | | | |
(13)PX BLOCK (ITERATOR)
以数据块范围为划分粒度,对一段范围的数据块进行迭代。
(14)PX PARTITION (LIST ALL)
以分区为划分粒度,并行服务进程访问一个列举分区,所有分区都会被访问。
(15)PX PARTITION (RANGE (ALL))
以分区为划分粒度,并行服务进程访问一个范围分区,所有分区都会被访问。
(16)PX PARTITION (MULTI-COLUMN)
以分区为划分粒度,并行服务进程访问一个以多字段为分区键的范围分区,并对分区键做裁剪,只访问匹配的分区。
(17)PX PARTITION (HASH (ALL))
以分区为划分粒度,并行服务进程访问一个哈希分区,所有分区都会被访问。
(18)HASH JOIN (BUFFERED)
生产者(Producer)将数据分发给相同哈希值的消费者(Consumer),由消费者在私有内存中对数据进行哈希关联。
(19)HASH JOIN (RIGHT SEMI BUFFERED)
生产者(Producer)将数据分发给相同哈希值的消费者(Consumer),由消费者在私有内存中对数据进行哈希不完整关联操作。示例如代码清单2-20所示。
代码清单2-20 HASH JOIN (RIGHT SEMI BUFFERED)示例
HELLODBA.COM>exec sql_explain('select /+parallel(o 2)/* from t_objects o where exists (select
/*+hash_sj PQ_DISTRIBUTE(t HASH HASH)*/1 from t_tables t where o.owner = t.owner and
o.object_name = t.table_name)', 'TYPICAL');
Plan hash value: 170661742
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
| 0 | SELECT STATEMENT | | 23 | 3243 | 33 (4)| 00:00:33 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 23 | 3243 | 33 (4)| 00:00:33 | Q1,02 | P->S | QC (RAND) |
|* 3 | HASH JOIN RIGHT SEMI BUFFERED| | 23 | 3243 | 33 (4)| 00:00:33 | Q1,02 | PCWP | |
| 4 | BUFFER SORT | | | | | | Q1,02 | PCWC | |
| 5 | PX RECEIVE | | 2070 | 49680 | 2 (0)| 00:00:03 | Q1,02 | PCWP | |
| 6 | PX SEND HASH | :TQ10000 | 2070 | 49680 | 2 (0)| 00:00:03 | | S->P | HASH |
| 7 | INDEX FAST FULL SCAN | T_TABLES_PK | 2070 | 49680 | 2 (0)| 00:00:03 | | | |
| 8 | PX RECEIVE | | 47585 | 5436K| 30 (0)| 00:00:31 | Q1,02 | PCWP | |
| 9 | PX SEND HASH | :TQ10001 | 47585 | 5436K| 30 (0)| 00:00:31 | Q1,01 | P->P | HASH |
| 10 | PX BLOCK ITERATOR | | 47585 | 5436K| 30 (0)| 00:00:31 | Q1,01 | PCWC | |
| 11 | TABLE ACCESS FULL | T_OBJECTS | 47585 | 5436K| 30 (0)| 00:00:31 | Q1,01 | PCWP | |
Predicate Information (identified by operation id):
3 - access("O"."OWNER"="T"."OWNER" AND "O"."OBJECT_NAME"="T"."TABLE_NAME")
(20)HASH JOIN (RIGHT ANTI BUFFERED)
生产者(Producer)将数据分发给相同哈希值的消费者(Consumer),由消费者在私有内存中对数据进行哈希反关联操作。
2.4.11 聚合操作
此类操作对数据集进行聚合,获取相应数据。
(1)HASH (GROUP BY)
通过哈希计算对数值进行分组。
提示:在HASH(GROUP BY)出现(10gR2)之前,在对数据进行分组时,是先将数据排序,然后再获取分组数据,即SORT(GROUP BY)。要启用HASH(GROUP BY),需要确保优化器参数_gby_hash_aggregation_enabled为TRUE。
示例如下:
HELLODBA.COM>alter session set "_gby_hash_aggregation_enabled"=true;
Session altered.
HELLODBA.COM>exec sql_explain('select owner, count(1) from t_tables group by owner','TYPICAL');
Plan hash value: 3517041855
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 21 | 126 | 3 (34)| 00:00:04 |
| 1 | HASH GROUP BY | | 21 | 126 | 3 (34)| 00:00:04 |
| 2 | INDEX FAST FULL SCAN| T_TABLES_IDX1 | 2070 | 12420 | 2 (0)| 00:00:03 |
(2)HASH (UNIQUE)
通过哈希计算获取唯一数值。
(3)GENERATE (CUBE)
生成CUBE。CUBE可以在多个维度上,以及维度之间的组合上做聚集计算。
示例如下:
HELLODBA.COM>exec sql_explain('select owner, tablespace_name, count(1) from t_tables group by
cube(owner,tablespace_name)', 'TYPICAL');
Plan hash value: 2209870883
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 149 | 1937 | 7 (15)| 00:00:08 |
| 1 | SORT GROUP BY | | 149 | 1937 | 7 (15)| 00:00:08 |
| 2 | GENERATE CUBE | | 149 | 1937 | 7 (15)| 00:00:08 |
| 3 | SORT GROUP BY | | 149 | 1937 | 7 (15)| 00:00:08 |
| 4 | TABLE ACCESS FULL| T_TABLES | 2070 | 26910 | 6 (0)| 00:00:07 |
2.4.12 分析函数操作
下面介绍用于计算分析函数结果的操作。
(1)WINDOW (BUFFER)
分析函数(Analytic Function)在窗口内存中进行聚集计算。
关键词释义
分析函数(Analytic Function):基于一组数据计算出聚集结果。这样一组数据就称为一个窗口(window),由分析函数中的OVER ( ... )子句定义。
通过分析函数可以避免由复杂的执行操作(如关联)得出分析数据。示例如下:
HELLODBA.COM>exec sql_explain('select table_name, count(1) over (partition by table_name) cnt from
t_tables t', 'TYPICAL');
Plan hash value: 4083014614
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 2070 | 37260 | 7 (0)| 00:00:07 |
| 1 | WINDOW BUFFER | | 2070 | 37260 | 7 (0)| 00:00:07 |
| 2 | INDEX FULL SCAN| T_TABLES_PK | 2070 | 37260 | 7 (0)| 00:00:07 |
(2)WINDOW (SORT)
分析函数(Analytic Function)在窗口内存中对数据排序后,再进行聚集计算。
(3)WINDOW (NOSORT)
分析函数(Analytic Function)在窗口内存中对数据按照数据的物理顺序(或者数据结果有顺序要求,但数据物理顺序与逻辑顺序一致而无需再做排序)进行聚集计算。
(4)WINDOW (SORT PUSHED RANK)
子查询中的分析函数(Analytic Function)在窗口内存中对数据排序时,主查询中的谓词条件被推入子查询或视图中序列函数上,作为子查询或视图的过滤条件。示例如下:
HELLODBA.COM>exec sql_explain('select from (select /+index_ffs(T T_TABLES_IDX1)*/owner, row_number()
over (order by owner) rnum from t_tables t) where rnum<=10', 'TYPICAL');
Plan hash value: 603754030
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 2070 | 62100 | 3 (34)| 00:00:04 |
|* 1 | VIEW | | 2070 | 62100 | 3 (34)| 00:00:04 |
|* 2 | WINDOW SORT PUSHED RANK| | 2070 | 12420 | 3 (34)| 00:00:04 |
| 3 | INDEX FAST FULL SCAN | T_TABLES_IDX1 | 2070 | 12420 | 2 (0)| 00:00:03 |
Predicate Information (identified by operation id):
1 - filter("RNUM"<=10)
2 - filter(ROW_NUMBER() OVER ( ORDER BY "OWNER")<=10)
从以上执行计划中的谓词信息中可以看到PUSH之后的过滤条件。
(5)WINDOW (NOSORT STOPKEY)
分析函数(Analytic Function)在窗口内存中按照物理顺序处理数据时,处理特定记录数后停止。示例如下:
HELLODBA.COM>exec sql_explain('select from (select /+index(T T_TABLES_IDX1)*/owner, row_number()
over (order by owner) rnum from t_tables t) where rnum<=10', 'TYPICAL');
Plan hash value: 2520140641
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 2070 | 62100 | 4 (0)| 00:00:04 |
|* 1 | VIEW | | 2070 | 62100 | 4 (0)| 00:00:04 |
|* 2 | WINDOW NOSORT STOPKEY| | 2070 | 12420 | 4 (0)| 00:00:04 |
| 3 | INDEX FULL SCAN | T_TABLES_IDX1 | 2070 | 12420 | 4 (0)| 00:00:04 |
Predicate Information (identified by operation id):
1 - filter("RNUM"<=10)
2 - filter(ROW_NUMBER() OVER ( ORDER BY "OWNER")<=10)
(6)WINDOW(IN SQL MODEL (SORT))
依据模型化SQL语句中的规则,建立窗口,对数据排序,进行聚集计算。示例如下:
HELLODBA.COM>begin
2 sql_explain('SELECT statistic#, s
3 FROM t_sesstat
4 MODEL RETURN UPDATED ROWS
5 PARTITION BY (statistic#)
6 DIMENSION BY (sid)
7 MEASURES (value v, 1 s)
8 RULES
9 (
10 s[any] = sum(v) over (partition by statistic#)
11 )','TYPICAL');
12 end;
13 /
Plan hash value: 3747799581
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 7280 | 65520 | 3 (0)| 00:00:04 |
| 1 | SQL MODEL ORDERED | | 7280 | 65520 | | |
| 2 | TABLE ACCESS FULL | T_SESSTAT | 7280 | 65520 | 3 (0)| 00:00:04 |
| 3 | WINDOW (IN SQL MODEL) SORT| | | | | |
2.4.13 模型化操作
该类操作是对数据集进行模型化(Model)处理的操作。
关键词释义
模型化SQL语句(SQL MODEL)可以在一个或多个数据维度(DIMESION)上,使用各种公式建立规则(RULES),实现类似电子表格的计算功能。
(1)SQL MODEL (ACYCLIC)
执行模型化SQL语句,按照规则在RULES子句中出现的顺序,并遵循规则的定义,使用分析函数进行计算。示例如下:
HELLODBA.COM>begin
2 sql_explain('SELECT sid, statistic#, v,s
3 FROM t_sesstat
4 MODEL
5 PARTITION BY (sid)
6 DIMENSION BY (statistic#)
7 MEASURES (value v, 1 s)
8 RULES AUTOMATIC ORDER
9 (
10 s[any] = v[CURRENTV()]*1.05
11 )
12 order by sid, statistic#','TYPICAL');
13 end;
14 /
Plan hash value: 296212948
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 7280 | 65520 | | 30 (4)| 00:00:31 |
| 1 | SORT ORDER BY | | 7280 | 65520 | 296K| 30 (4)| 00:00:31 |
| 2 | SQL MODEL ACYCLIC | | 7280 | 65520 | | 30 (4)| 00:00:31 |
| 3 | TABLE ACCESS FULL| T_SESSTAT | 7280 | 65520 | | 3 (0)| 00:00:04 |
(2)SQL MODEL (ACYCLIC FAST)
执行模型化SQL语句,Oracle自动选择RULES子句的规则顺序,并遵循规则的定义,快速访问其他数据行(或依据规则计算出来的数据行),还使用分析函数进行计算。示例如下:
HELLODBA.COM> begin
2 sql_explain('SELECT sid,statistic#,v
3 FROM t_sesstat
4 MODEL RETURN UPDATED ROWS
5 partition by (sid)
6 DIMENSION BY (statistic#)
7 MEASURES (value v)
8 RULES AUTOMATIC ORDER
9 (
10 v[999] = v[52]+v[64]
11 )
12 ORDER BY sid, statistic#','TYPICAL');
13 end;
14 /
Plan hash value: 16555562
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 7280 | 65520 | | 30 (4)| 00:00:31 |
| 1 | SORT ORDER BY | | 7280 | 65520 | 296K| 30 (4)| 00:00:31 |
| 2 | SQL MODEL ACYCLIC FAST| | 7280 | 65520 | | 30 (4)| 00:00:31 |
| 3 | TABLE ACCESS FULL | T_SESSTAT | 7280 | 65520 | | 3 (0)| 00:00:04 |
(3)SQL MODEL (ORDERED)
执行模型化SQL语句,按照规则在RULES子句中出现的顺序,并遵循规则的定义,对数据进行计算。示例如下:
HELLODBA.COM>begin
2 sql_explain('SELECT sid, statistic#, v,s
3 FROM t_sesstat
4 MODEL
5 PARTITION BY (sid)
6 DIMENSION BY (statistic#)
7 MEASURES (value v, 1 s)
8 RULES SEQUENTIAL ORDER
9 (
10 s[any] = v[CURRENTV()]*1.05
11 )','TYPICAL');
12 end;
13 /
Plan hash value: 1590488882
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 7280 | 65520 | 3 (0)| 00:00:04 |
| 1 | SQL MODEL ORDERED | | 7280 | 65520 | | |
| 2 | TABLE ACCESS FULL| T_SESSTAT | 7280 | 65520 | 3 (0)| 00:00:04 |
PL/SQL procedure successfully completed.
(4)SQL MODEL (ORDERED FAST)
执行模型化SQL语句,按照规则在RULES子句中出现的顺序,并遵循规则的定义,快速访问其他数据行(或依据规则计算出来的数据行),对数据进行计算。示例如下:
HELLODBA.COM> begin
2 sql_explain('SELECT sid,statistic#,v
3 FROM t_sesstat
4 MODEL RETURN UPDATED ROWS
5 partition by (sid)
6 DIMENSION BY (statistic#)
7 MEASURES (value v)
8 RULES SEQUENTIAL ORDER
9 (
10 v[999] = v[52]+v[64]
11 )
12 ORDER BY sid, statistic#','TYPICAL');
13 end;
14 /
Plan hash value: 537697963
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 7280 | 65520 | | 30 (4)| 00:00:31 |
| 1 | SORT ORDER BY | | 7280 | 65520 | 296K| 30 (4)| 00:00:31 |
| 2 | SQL MODEL ORDERED FAST| | 7280 | 65520 | | 30 (4)| 00:00:31 |
| 3 | TABLE ACCESS FULL | T_SESSTAT | 7280 | 65520 | | 3 (0)| 00:00:04 |
(5)REFERENCE MODEL
执行模型化SQL语句,并且引用其他的模型化SQL语句。示例如代码清单2-21所示。
引用模型(REFERENCE MODEL)作为主模型(MAIN MODEL)的维度,可以有一到多个,并且为只读,即主模型可以在规则中引用它们的数据单元,但是不能修改它们的数据。
代码清单2-21 REFERENCE MODEL示例
HELLODBA.COM> begin
2 sql_explain('SELECT owner, tablespace_name, tbcnt, tt
3 FROM t_tables
4 GROUP BY owner,tablespace_name
5 MODEL
6 REFERENCE usr ON
7 (
8 SELECT username, default_tablespace FROM t_users
9 )
10 DIMENSION BY (username)
11 MEASURES (default_tablespace d) IGNORE NAV
12 MAIN tab
13 DIMENSION BY (owner, tablespace_name)
14 MEASURES (count(1) tbcnt, LPAD(''A'',30) tt) IGNORE NAV
15 RULES
16 (
17 tt[any,any] = d[CV(owner)]
18 )
19 ORDER BY owner, tablespace_name','TYPICAL');
20 end;
21 /;
Plan hash value: 1072175013
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 149 | 1937 | 8 (25)| 00:00:09 |
| 1 | SORT ORDER BY | | 149 | 1937 | 8 (25)| 00:00:09 |
| 2 | SQL MODEL ORDERED | | 149 | 1937 | 8 (25)| 00:00:09 |
| 3 | REFERENCE MODEL | USR | 41 | 1394 | 2 (0)| 00:00:03 |
| 4 | TABLE ACCESS FULL| T_USERS | 41 | 1394 | 2 (0)| 00:00:03 |
| 5 | HASH GROUP BY | | 149 | 1937 | 8 (25)| 00:00:09 |
| 6 | TABLE ACCESS FULL| T_TABLES | 2070 | 26910 | 6 (0)| 00:00:07 |
2.4.14 数据和对象管理操作
此类操作会造成数据或对象的改变。
(1)MULTI-TABLE INSERT
将数据同时插入多个表中,示例如下:
HELLODBA.COM>exec sql_explain('insert all into t_tables_sub1 into t_tables_sub2 select * from
t_tables', 'TYPICAL');
Plan hash value: 3519009192
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | INSERT STATEMENT | | 2070 | 412K| 6 (0)| 00:00:07 |
| 1 | MULTI-TABLE INSERT | | | | | |
| 2 | INTO | T_TABLES_SUB1 | | | | |
| 3 | INTO | T_TABLES_SUB2 | | | | |
| 4 | TABLE ACCESS FULL| T_TABLES | 2070 | 412K| 6 (0)| 00:00:07 |
(2)INTO
多表插入时,将数据插入其中一张表。
(3)DIRECT LOAD INTO
将数据直接载入表中。
提示:直接载入数据时,无论高水位线(High Water Mark)以下是否存在空闲数据块或未使用的数据块,都会分配新的扩展段给新插入的数据。
示例如下:
HELLODBA.COM>exec sql_explain('insert /+append/all into t_tables_sub1 into t_tables_sub2 select *
from t_tables', 'TYPICAL');
Plan hash value: 3519009192
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | INSERT STATEMENT | | 2070 | 412K| 6 (0)| 00:00:07 |
| 1 | MULTI-TABLE INSERT | | | | | |
| 2 | DIRECT LOAD INTO | T_TABLES_SUB1 | | | | |
| 3 | DIRECT LOAD INTO | T_TABLES_SUB2 | | | | |
| 4 | TABLE ACCESS FULL| T_TABLES | 2070 | 412K| 6 (0)| 00:00:07 |
(4)INDEX BUILD (NON UNIQUE)
构建非唯一索引。
(5)INDEX BUILD (UNIQUE)
构建唯一索引。
(6)INDEX BUILD (NON UNIQUE (LOCAL))
构建本地非唯一索引。
(7)INDEX BUILD (UNIQUE (LOCAL))
构建本地唯一索引。
注意,本地唯一索引必须包含分区字段。
(8)INDEX MAINTENANCE
在进行并行DML操作时,维护表中的索引。示例如代码清单2-22所示。
代码清单2-22 INDEX MAINTENANCE示例
HELLODBA.COM>alter session enable parallel dml;
Session altered.
HELLODBA.COM>exec sql_explain('delete /+parallel(o)/from t_xpl o','TYPICAL');
Plan hash value: 2365219202
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
| 0 | DELETE STATEMENT | | 1 | 25 | 2 (0)| 00:00:03 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10001 | 1 | 25 | 2 (0)| 00:00:03 | Q1,01 | P->S | QC (RAND) |
| 3 | INDEX MAINTENANCE | T_XPL | | | | | Q1,01 | PCWP | |
| 4 | PX RECEIVE | | 1 | 25 | 2 (0)| 00:00:03 | Q1,01 | PCWP | |
| 5 | PX SEND RANGE | :TQ10000 | 1 | 25 | 2 (0)| 00:00:03 | Q1,00 | P->P | RANGE |
| 6 | DELETE | T_XPL | | | | | Q1,00 | PCWP | |
| 7 | PX BLOCK ITERATOR | | 1 | 25 | 2 (0)| 00:00:03 | Q1,00 | PCWC | |
| 8 | TABLE ACCESS FULL| T_XPL | 1 | 25 | 2 (0)| 00:00:03 | Q1,00 | PCWP | |
注意,如果被更新的表中不存在索引,则这个操作不会出现在执行计划中。
(9)BITMAP CONSTRUCTION
在创建位图索引过程中构造位图。
2.4.15 其他操作
所有未归类的操作均放入该类。
(1)COUNT
通过计数器对获取到的数据记录计数,以产生伪列ROWNUM的数值。示例如下:
HELLODBA.COM>exec sql_explain('select owner, table_name, rownum from t_tables','TYPICAL');
Plan hash value: 1881146282
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 2070 | 49680 | 2 (0)| 00:00:03 |
| 1 | COUNT | | | | | |
| 2 | INDEX FAST FULL SCAN| T_TABLES_PK | 2070 | 49680 | 2 (0)| 00:00:03 |
(2)COUNT (STOPKEY)
通过计数器对获取到的数据记录计数,以产生伪列ROWNUM的数值,并且当记录数达到条件时停止计数和数据的读取。
(3)INLIST ITERATOR
对IN条件中的数值进行迭代,分别通过索引获取满足条件的数据集。
(4)FILTER
对已经读取的数据集进行过滤。
提示:对于查询中的条件,可以分为两类条件:一类为访问条件,即通过该条件可以定位到数据的物理位置,例如索引字段匹配;另一类为过滤条件,即需要在获取数据(Fetch)时再次进行过滤,以满足查询条件。而我们这里的过滤,则是对一个已经读取到的结果集再次进行过滤。
示例如下:
HELLODBA.COM>exec sql_explain('select owner, count(1) from t_tables group by owner having
count(1)<10','TYPICAL');
Plan hash value: 231252140
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 2 | 12 | 3 (34)| 00:00:04 |
|* 1 | FILTER | | | | | |
| 2 | SORT GROUP BY | | 2 | 12 | 3 (34)| 00:00:04 |
| 3 | INDEX FAST FULL SCAN| T_TABLES_IDX1 | 2070 | 12420 | 2 (0)| 00:00:03 |
Predicate Information (identified by operation id):
1 - filter(COUNT(*)<10)
(5)FIRST ROW
仅读取访问到的第一条数据,并立即返回。
通过索引读取前导索引字段的最大值或最小值时,由于数据已经排序,因此第一条数据就是需要的数据,无需再读取剩余数据。示例如下:
HELLODBA.COM>exec sql_explain('select max(table_name) from t_tables where table_name < :A','TYPICAL');
Plan hash value: 1276389898
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 1 | 18 | 1 (0)| 00:00:02 |
| 1 | SORT AGGREGATE | | 1 | 18 | | |
| 2 | FIRST ROW | | 104 | 1872 | 1 (0)| 00:00:02 |
|* 3 | INDEX RANGE SCAN (MIN/MAX)| T_TABLES_PK | 104 | 1872 | 1 (0)| 00:00:02 |
Predicate Information (identified by operation id):
3 - access("TABLE_NAME"<:A)
(6)FOR UPDATE
将读取到的数据记录加锁,等待事务的后续语句对数据进行更新。
(7)COLLECTION ITERATOR (CONSTRUCTOR FETCH)
对一个构造出来的集合对象中的成员进行迭代取值。
从集合对象中查询数据,需要通过表映射函数(TABLE)进行映射。示例如下:
HELLODBA.COM>exec sql_explain('select * from
table(phone_list(phone(111111),phone(2222222),phone(333333)))','TYPICAL');
Plan hash value: 1748000095
| Id | Operation | Name |
| 0 | SELECT STATEMENT | |
| 1 | COLLECTION ITERATOR CONSTRUCTOR FETCH| |
(8)COLLECTION ITERATOR(PICKLER FETCH)
对一个集合对象中的成员进行迭代取值。示例如下:
HELLODBA.COM>exec sql_explain('select * from table(get_phonelist())','TYPICAL');
Plan hash value: 3785905542
| Id | Operation | Name |
| 0 | SELECT STATEMENT | |
| 1 | COLLECTION ITERATOR PICKLER FETCH| GET_PHONELIST |
(9)COLLECTION ITERATOR (SUBQUERY FETCH)
将一个子查询结果集映射成集合,并对集合对象中的成员进行迭代取值。示例如下:
HELLODBA.COM>exec sql_explain('select /+no_merge(t)/* from table(cast(multiset(select
phone(123456+user_id) from t_users) as phone_list)) t','TYPICAL');
Plan hash value: 805297652
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 8168 | 207K| 8 (0)| 00:00:09 |
| 1 | VIEW | | 8168 | 207K| 8 (0)| 00:00:09 |
| 2 | COLLECTION ITERATOR SUBQUERY FETCH| | | | | |
| 3 | INDEX FULL SCAN | T_USERS_PK | 41 | 82 | 1 (0)| 00:00:02 |
(10)SEQUENCE
访问序列(SEQUENCE)对象。
(11)REMOTE
访问远程数据库上的对象。示例如下:
HELLODBA.COM>create database link ORA10201 connect to demo identified by demo using 'ORA10201';
Database link created.
HELLODBA.COM>exec sql_explain('select from t_users@ora10201 union select from t_users','TYPICAL');
Plan hash value: 567843309
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Inst |IN-OUT|
| 0 | SELECT STATEMENT | | 82 | 7380 | 6 (67)| 00:00:07 | | |
| 1 | SORT UNIQUE | | 82 | 7380 | 6 (67)| 00:00:07 | | |
| 2 | UNION-ALL | | | | | | | |
| 3 | REMOTE | T_USERS | 41 | 3690 | 2 (0)| 00:00:03 | ORA10~ | R->S |
| 4 | TABLE ACCESS FULL| T_USERS | 41 | 3690 | 2 (0)| 00:00:03 | | |
Remote SQL Information (identified by operation id):
3 - SELECT "USERNAME","USER_ID","PASSWORD","ACCOUNT_STATUS","LOCK_DATE","EXPIRY_DATE
","DEFAULT_TABLESPACE","TEMPORARY_TABLESPACE","CREATED","PROFILE","INITIAL_RSRC_CONSUME
R_GROUP","EXTERNAL_NAME" FROM "T_USERS" "T_USERS" (accessing 'ORA10201' )
提示:在分布式查询的执行计划中,会多出两列数据,Inst为远程对象所在实例名,IN-OUT为数据传输方式,R->S表示由远程传向本地串行操作。
(12)SEQUENCE (REMOTE)
访问远程数据库上的序列(SEQUENCE)对象。
(13)TEMP TABLE TRANSFORMATION
对语句执行过程中产生的临时表进行转换。示例如代码清单2-23所示。
代码清单2-23 TEMP TABLE TRANSFORMATION示例
HELLODBA.COM>exec sql_explain('select 1 from dual group by cube(1,1)', 'TYPICAL');
Plan hash value: 3618534127
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 0 | SELECT STATEMENT | | 1 | 13 | 10 (0)| 00:00:11 |
| 1 | TEMP TABLE TRANSFORMATION | | | | | |
| 2 | MULTI-TABLE INSERT | | | | | |
| 3 | DIRECT LOAD INTO | SYS_TEMP_0FD9D662A_EECFFE34 | | | | |
| 4 | DIRECT LOAD INTO | SYS_TEMP_0FD9D662B_EECFFE34 | | | | |
| 5 | SORT GROUP BY NOSORT ROLLUP| | 1 | | 2 (0)| 00:00:03 |
| 6 | FAST DUAL | | 1 | | 2 (0)| 00:00:03 |
| 7 | VIEW | | 4 | 52 | 8 (0)| 00:00:09 |
| 8 | VIEW | | 4 | 52 | 8 (0)| 00:00:09 |
| 9 | UNION-ALL | | | | | |
| 10 | TABLE ACCESS FULL | SYS_TEMP_0FD9D662A_EECFFE34 | 1 | 13 | 2 (0)| 00:00:03 |
| 11 | TABLE ACCESS FULL | SYS_TEMP_0FD9D662A_EECFFE34 | 1 | 13 | 2 (0)| 00:00:03 |
| 12 | TABLE ACCESS FULL | SYS_TEMP_0FD9D662A_EECFFE34 | 1 | 13 | 2 (0)| 00:00:03 |
| 13 | TABLE ACCESS FULL | SYS_TEMP_0FD9D662B_EECFFE34 | 1 | 13 | 2 (0)| 00:00:03 |
(14)APPROXIMATE NDV AGGREGATE
唯一值数(Number of Distinct Value,NDV)估计。这是11g中新出现的操作,基于哈希算法对字段的NDV数值进行估算(我们在后面章节会介绍这一算法)。并且该操作仅出现在对象分析过程中执行的相关语句中。示例如代码清单2-24所示(其中游标是在表分析过程中产生)。
代码清单2-24 APPROXIMATE NDV AGGREGATE示例
HELLODBA.COM>select * from table(dbms_xplan.display_cursor('79ct3jc2bd427',0,'TYPICAL'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------
SQL_ID 79ct3jc2bd427, child number 0
-------------------------------------
/* SQL Analyze(0) */ select /*+ full(t) no_parallel(t)
no_parallel_index(t) dbms_stats cursor_sharing_exact use_weak_name_resl
dynamic_sampling(0) no_monitoring no_substrb_pad
*/to_char(count("OWNER")),to_char(substrb(dump(min("OWNER"),16,0,32),1,1
20)),to_char(substrb(dump(max("OWNER"),16,0,32),1,120)),to_char(count("O
BJECT_NAME")),to_char(substrb(dump(min("OBJECT_NAME"),16,0,32),1,120)),t
o_char(substrb(dump(max("OBJECT_NAME"),16,0,32),1,120)),to_char(count("S
UBOBJECT_NAME")),to_char(substrb(dump(min("SUBOBJECT_NAME"),16,0,32),1,1
20)),to_char(substrb(dump(max("SUBOBJECT_NAME"),16,0,32),1,120)),to_char
(count("OBJECT_ID")),to_char(substrb(dump(min("OBJECT_ID"),16,0,32),1,12
0)),to_char(substrb(dump(max("OBJECT_ID"),16,0,32),1,120)),to_char(count
("DATA_OBJECT_ID")),to_char(substrb(dump(min("DATA_OBJECT_ID"),16,0,32),
1,120)),to_char(substrb(dump(max("DATA_OBJECT_ID"),16,0,32),1,120)),to_c
har(count("OBJECT_TYPE")),to_char(substrb(dump(min("OBJECT_TYPE"),16,0,3
2),1,120)),to_char(substrb(dump(
Plan hash value: 817774313
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 289 (100)| |
| 1 | SORT AGGREGATE | | 1 | 99 | | |
| 2 | APPROXIMATE NDV AGGREGATE| | 72115 | 6972K| 289 (1)| 00:00:04 |
| 3 | TABLE ACCESS FULL | T_OBJECTS | 72115 | 6972K| 289 (1)| 00:00:04 |
----------------------------------------------------------------------------------------