Oracle索引扫描的五种类型
根据索引的类型与where限制条件的不同,有5种类型的Oracle索引扫描:
(1)索引唯一扫描(indexuniquescan)
(2)索引范围扫描(indexrangescan)
(3)索引全扫描(indexfullscan)
(4)索引快速扫描(indexfastfullscan)
(5)索引跳跃扫描(INDEXSKIPSCAN)
一.索引唯一扫描(indexuniquescan)
通过唯一索引查找一个数值经常返回单个ROWID。如果该唯一索引有多个列组成(即组合索引),则至少要有组合索引的引导列参与到该查询中,如创建一个索引:createindexidx_testonemp(ename,deptno,loc)。则selectenamefromempwhereename=‘JACK’anddeptno=‘DEV’语句可以使用该索引。如果该语句只返回一行,则存取方法称为索引唯一扫描。而selectenamefromempwheredeptno=‘DEV’语句则不会使用该索引,因为where子句种没有引导列。如果存在UNIQUE或PRIMARYKEY约束(它保证了语句只存取单行)的话,Oracle经常实现唯一性扫描。
如:
SQL>setautottraceonlyexp;--只显示执行计划
SQL>select*fromscott.emptwheret.empno=10;
执行计划
----------------------------------------------------------
Planhashvalue:2949544139
--------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time
--------------------------------------------------------------------------------
|0|SELECTSTATEMENT||1|38|1(0)|00:0
|1|TABLEACCESSBYINDEXROWID|EMP|1|38|1(0)|00:0
|*2|INDEXUNIQUESCAN|PK_EMP|1||0(0)|00:0
--------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
2-access("T"."EMPNO"=10)
二.索引范围扫描(indexrangescan)
使用一个索引存取多行数据,同上面一样,如果索引是组合索引,而且selectenamefromempwhereename=‘JACK’anddeptno=‘DEV’语句返回多行数据,虽然该语句还是使用该组合索引进行查询,可此时的存取方法称为索引范围扫描。
在唯一索引上使用索引范围扫描的典型情况下是在谓词(where限制条件)中使用了范围操作符(如>、<、<>、>=、<=、between)
使用索引范围扫描的例子:
SQL>selectempno,enamefromscott.empwhereempno>7876orderbyempno;
执行计划
----------------------------------------------------------
Planhashvalue:169057108
--------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time
--------------------------------------------------------------------------------
|0|SELECTSTATEMENT||1|10|2(0)|00:0
|1|TABLEACCESSBYINDEXROWID|EMP|1|10|2(0)|00:0
|*2|INDEXRANGESCAN|PK_EMP|1||1(0)|00:0
--------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
2-access("EMPNO">7876)
在非唯一索引上,谓词可能返回多行数据,所以在非唯一索引上都使用索引范围扫描。
使用indexrangscan的3种情况:
(a)在唯一索引列上使用了range操作符(><<>>=<=between)。
(b)在组合索引上,只使用部分列进行查询,导致查询出多行。
(c)对非唯一索引列上进行的任何查询。
三.索引全扫描(indexfullscan)
与全表扫描对应,也有相应的全Oracle索引扫描。在某些情况下,可能进行全Oracle索引扫描而不是范围扫描,需要注意的是全Oracle索引扫描只在CBO模式下才有效。CBO根据统计数值得知进行全Oracle索引扫描比进行全表扫描更有效时,才进行全Oracle索引扫描,而且此时查询出的数据都必须从索引中可以直接得到。
全Oracle索引扫描的例子:
SQL>createindexbig_emponscott.emp(empno,ename);
索引已创建。
SQL>selectempno,enamefromscott.emporderbyempno,ename;
执行计划
----------------------------------------------------------
Planhashvalue:322359667
----------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
----------------------------------------------------------------------------
|0|SELECTSTATEMENT||14|140|1(0)|00:00:01|
|1|INDEXFULLSCAN|BIG_EMP|14|140|1(0)|00:00:01|
----------------------------------------------------------------------------
四.索引快速扫描(indexfastfullscan)
扫描索引中的所有的数据块,与indexfullscan很类似,但是一个显著的区别就是它不对查询出的数据进行排序,即数据不是以排序顺序被返回。在这种存取方法中,可以使用多块读功能,也可以使用并行读入,以便获得最大吞吐量与缩短执行时间。
索引快速扫描的例子:
SQL>select/*+index_ffs(daveindex_dave)*/idfromdavewhereid>0;
执行计划
----------------------------------------------------------
Planhashvalue:674200218
--------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time
--------------------------------------------------------------------------------
|0|SELECTSTATEMENT||8|24|2(0)|00:00:0
|*1|INDEXFASTFULLSCAN|INDEX_DAVE|8|24|2(0)|00:00:0
--------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
1-filter("ID">0)
为了实现这个效果,折腾了半天,最终还是用hint来了.
OracleHint
http://blog.csdn.net/tianlesoftware/archive/2010/03/05/5347098.aspx
五.索引跳跃扫描(INDEXSKIPSCAN)
INDEXSKIPSCAN,发生在多个列建立的复合索引上,如果SQL中谓词条件只包含索引中的部分列,并且这些列不是建立索引时的第一列时,就可能发生INDEXSKIPSCAN。这里SKIP的意思是因为查询条件没有第一列或前面几列,被忽略了。
Oracle10g的文档如下:
Indexskipscansimproveindexscansbynonprefixcolumns.Often,scanningindexblocksisfasterthanscanningtabledatablocks.
Skipscanningletsacompositeindexbesplitlogicallyintosmallersubindexes.Inskipscanning,theinitialcolumnofthecompositeindexisnotspecifiedinthequery.Inotherwords,itisskipped.
--skipscan让组合索引(compositeindex)逻辑的split成几个子索引。如果在在查询时,第一个列没有指定,就跳过它。
Thenumberoflogicalsubindexesisdeterminedbythenumberofdistinctvaluesintheinitialcolumn.Skipscanningisadvantageousiftherearefewdistinctvaluesintheleadingcolumnofthecompositeindexandmanydistinctvaluesinthenonleadingkeyoftheindex.
--建议将distinct值小的列作为组合索引的引导列,即第一列。
Example13-5IndexSkipScan
Consider,forexample,atableemployees(sex,employee_id,address)withacompositeindexon(sex,employee_id).Splittingthiscompositeindexwouldresultintwologicalsubindexes,oneforMandoneforF.
Forthisexample,supposeyouhavethefollowingindexdata:
('F',98)
('F',100)
('F',102)
('F',104)
('M',101)
('M',103)
('M',105)
Theindexissplitlogicallyintothefollowingtwosubindexes:
(1)ThefirstsubindexhasthekeyswiththevalueF.
(2)ThesecondsubindexhasthekeyswiththevalueM.
Figure13-2IndexSkipScanIllustration
Thecolumnsexisskippedinthefollowingquery:
SELECT*
FROMemployees
WHEREemployee_id=101;
Acompletescanoftheindexisnotperformed,butthesubindexwiththevalueFissearchedfirst,followedbyasearchofthesubindexwiththevalueM.
测试:
创建表:
SQL>createtabledave_testasselectowner,object_id,object_type,createdfromdba_objects;
Tablecreated.
创建组合索引
SQL>createindexidx_dave_test_comondave_test(owner,object_id,object_type);
Indexcreated.
--收集表的统计信息
SQL>execdbms_stats.gather_table_stats('SYS','DAVE_TEST');
PL/SQLproceduresuccessfullycompleted.
SQL>setautottraceonlyexp;
指定组合索引的所有字段时,使用Indexrangescan:
SQL>select*fromdave_testwhereowner='SYS'andobject_id=20andobject_type='TABLE';
ExecutionPlan
----------------------------------------------------------
Planhashvalue:418973243
--------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(
--------------------------------------------------------------------------------
|0|SELECTSTATEMENT||1|27|2
|1|TABLEACCESSBYINDEXROWID|DAVE_TEST|1|27|2
|*2|INDEXRANGESCAN|IDX_DAVE_TEST_COM|1||1
--------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
2-access("OWNER"='SYS'AND"OBJECT_ID"=20AND"OBJECT_TYPE"='TABLE')
指定组合索引的2个字段时,使用的还是indexrangescan:
SQL>select*fromdave_testwhereowner='SYS'andobject_id=20;
ExecutionPlan
----------------------------------------------------------
Planhashvalue:418973243
--------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(
--------------------------------------------------------------------------------
|0|SELECTSTATEMENT||1|27|3
|1|TABLEACCESSBYINDEXROWID|DAVE_TEST|1|27|3
|*2|INDEXRANGESCAN|IDX_DAVE_TEST_COM|1||2
--------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
2-access("OWNER"='SYS'AND"OBJECT_ID"=20)
指定组合索引的引导列,即第一个列时,不走索引,走全表扫描
SQL>select*fromdave_testwhereowner='SYS';
ExecutionPlan
----------------------------------------------------------
Planhashvalue:1539627441
-------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
-------------------------------------------------------------------------------
|0|SELECTSTATEMENT||23567|621K|52(4)|00:00:01|
|*1|TABLEACCESSFULL|DAVE_TEST|23567|621K|52(4)|00:00:01|
-------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
1-filter("OWNER"='SYS')
指定组合索引的非引导列,使用Indexskipscan:
SQL>select*fromdave_testwhereobject_id=20andobject_type='TABLE';
ExecutionPlan
----------------------------------------------------------
Planhashvalue:3446962311
--------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(
--------------------------------------------------------------------------------
|0|SELECTSTATEMENT||1|27|22
|1|TABLEACCESSBYINDEXROWID|DAVE_TEST|1|27|22
|*2|INDEXSKIPSCAN|IDX_DAVE_TEST_COM|1||21
--------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
2-access("OBJECT_ID"=20AND"OBJECT_TYPE"='TABLE')
filter("OBJECT_ID"=20AND"OBJECT_TYPE"='TABLE')
指定组合索引的最后一列,不走索引,走全表扫描
SQL>select*fromdave_testwhereobject_type='TABLE';
ExecutionPlan
----------------------------------------------------------
Planhashvalue:1539627441
-------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
-------------------------------------------------------------------------------
|0|SELECTSTATEMENT||1774|47898|52(4)|00:00:01|
|*1|TABLEACCESSFULL|DAVE_TEST|1774|47898|52(4)|00:00:01|
-------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
1-filter("OBJECT_TYPE"='TABLE')
指定组合索引的头尾2列,不走索引:
SQL>select*fromdave_testwhereowner='SYS'andobject_type='TABLE';
ExecutionPlan
----------------------------------------------------------
Planhashvalue:1539627441
-------------------------------------------------------------------------------
|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|
-------------------------------------------------------------------------------
|0|SELECTSTATEMENT||830|22410|52(4)|00:00:01|
|*1|TABLEACCESSFULL|DAVE_TEST|830|22410|52(4)|00:00:01|
-------------------------------------------------------------------------------
PredicateInformation(identifiedbyoperationid):
---------------------------------------------------
1-filter("OBJECT_TYPE"='TABLE'AND"OWNER"='SYS')
通过以上测试,和之前官网的说明,Indexskipscan仅是在组合索引的引导列,即第一列没有指定,并且非引导列指定的情况下。
联合索引选择性更高咯,所占空间应当是比单独索引要少,因为叶节点节省了重复的rowid,当然branch节点可能稍微多一点。
禁用skipscan:
altersystemset“_optimizer_skip_scan_enabled”=falsescope=spfile;