Oracle 学习之性能优化(六)访问路径
访问路径是指Oracle找到用户需要的数据的方法,这些方法很少,包括:
声名狼藉的全表扫描--人们不惜一切视图避免的(曲解的)访问路径。
各种类型的索引扫描--这是人们感觉良好的访问路径(多数情况下是被曲解的)。
通过hash或者rowid的方式直接访问,通常对于单数据行来说,是最快的。
并没有一种访问路径是最好的,如果有,那么Oracle只需提供这一种访问路径就好了。
全表扫描
全扫描就是顺序的读取表中的所有数据块。采用多块读的方式,从头开始扫描表中的块,直到高水位线。全扫描是处理大数据量行之有效的方法。需要牢记:全扫描并不邪恶,多数情况下全扫描是获得结果的最快方法。
全扫描每次读取的块数由参数db_file_multiblock_read_count指定
SQL>showparameterdb_file_muNAMETYPEVALUE---------------------------------------------------------------------------------------------------db_file_multiblock_read_countinteger128
2.rowid 访问
rowid是一行数据的物理位置,访问单行数据的速度是最快的。
SQL>select*fromempwhererowid='AAASZHAAEAAAACXAAN';7934MILLERCLERK77821982/01/2300:00:00130010
通过索引的方式访问数据,其实也是通过索引,先找到这行数据的rowid,然后再通过rowid访问数据。
SQL>setautotraceontraceonlySQL>select*fromempwhereempno=7934;ExecutionPlan----------------------------------------------------------Planhashvalue:2949544139--------------------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|--------------------------------------------------------------------------------------|0|SELECTSTATEMENT||1|38|1(0)|00:00:01||1|TABLEACCESSBYINDEXROWID|EMP|1|38|1(0)|00:00:01||*2|INDEXUNIQUESCAN|PK_EMP|1||0(0)|00:00:01|--------------------------------------------------------------------------------------
rowid还可以进行范围扫描。
SQL>select*fromempwhererowidbetween'AAASZHAAEAAAACXAAA'and'AAASZHAAEAAAACXAAN';7369SMITHCLERK79021980/12/1700:00:00800207499ALLENSALESMAN76981981/02/2000:00:001600300307521WARDSALESMAN76981981/02/2200:00:001250500307566JONESMANAGER78391981/04/0200:00:002975207654MARTINSALESMAN76981981/09/2800:00:0012501400307698BLAKEMANAGER78391981/05/0100:00:002850307782CLARKMANAGER78391981/06/0900:00:002450107788SCOTTANALYST75661987/04/1900:00:003000207839KINGPRESIDENT1981/11/1700:00:005000107844TURNERSALESMAN76981981/09/0800:00:0015000307876ADAMSCLERK77881987/05/2300:00:001100207900JAMESCLERK76981981/12/0300:00:00950307902FORDANALYST75661981/12/0300:00:003000207934MILLERCLERK77821982/01/2300:00:0013001014rowsselected.
3. 索引扫描
索引扫描是最常见的数据访问之一,例如
SQL>setautotraceontraceonlySQL>select*fromempwhereempno=7934;ExecutionPlan----------------------------------------------------------Planhashvalue:2949544139--------------------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|--------------------------------------------------------------------------------------|0|SELECTSTATEMENT||1|38|1(0)|00:00:01||1|TABLEACCESSBYINDEXROWID|EMP|1|38|1(0)|00:00:01||*2|INDEXUNIQUESCAN|PK_EMP|1||0(0)|00:00:01|--------------------------------------------------------------------------------------
我们下面主要以b-tree索引为例
索引唯一性扫描
优化器知道索引列的值是唯一的,查询结果只返回一行。这种索引的访问速度最快,找到一行数据就不再继续扫描索引,直接返回。
SQL>select*fromempwhereempno=7934;7934MILLERCLERK77821982/01/2300:00:00130010ExecutionPlan----------------------------------------------------------Planhashvalue:2949544139--------------------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|--------------------------------------------------------------------------------------|0|SELECTSTATEMENT||1|38|1(0)|00:00:01||1|TABLEACCESSBYINDEXROWID|EMP|1|38|1(0)|00:00:01||*2|INDEXUNIQUESCAN|PK_EMP|1||0(0)|00:00:01|--------------------------------------------------------------------------------------
实际上Oracle中并没有非唯一索引,在非唯一索引中,Oracle将数据的rowid添加到索引键中使其唯一。
索引范围扫描
SQL>setautottraceonlySQL>selectempnofromempwhereempno<5000;norowsselectedExecutionPlan----------------------------------------------------------Planhashvalue:1567865628---------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|---------------------------------------------------------------------------|0|SELECTSTATEMENT||1|4|1(0)|00:00:01||*1|INDEXRANGESCAN|PK_EMP|1|4|1(0)|00:00:01|---------------------------------------------------------------------------
有取的是,索引可以按照两个方向去扫描索引
SQL>selectempnofromempwhereempno<5000orderbyempno;norowsselectedExecutionPlan----------------------------------------------------------Planhashvalue:1567865628---------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|---------------------------------------------------------------------------|0|SELECTSTATEMENT||1|4|1(0)|00:00:01||*1|INDEXRANGESCAN|PK_EMP|1|4|1(0)|00:00:01|---------------------------------------------------------------------------PredicateInformation(identifiedbyoperationid):---------------------------------------------------1-access("EMPNO"<5000)SQL>selectempnofromempwhereempno<5000orderbyempnodesc;norowsselectedExecutionPlan----------------------------------------------------------Planhashvalue:2474278666--------------------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|--------------------------------------------------------------------------------------|0|SELECTSTATEMENT||1|4|1(0)|00:00:01||*1|INDEXRANGESCANDESCENDING|PK_EMP|1|4|1(0)|00:00:01|--------------------------------------------------------------------------------------
这个的好处是避免排序操作
如果你建立的是非唯一索引,即便你使用=查询,也是范围扫描
SQL>createindexind_emp_enameonemp(ename);Indexcreated.SQL>select*fromempwhereename='KING';ExecutionPlan----------------------------------------------------------Planhashvalue:2929622481---------------------------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|---------------------------------------------------------------------------------------------|0|SELECTSTATEMENT||1|38|2(0)|00:00:01||1|TABLEACCESSBYINDEXROWID|EMP|1|38|2(0)|00:00:01||*2|INDEXRANGESCAN|IND_EMP_ENAME|1||1(0)|00:00:01|---------------------------------------------------------------------------------------------PredicateInformation(identifiedbyoperationid):---------------------------------------------------2-access("ENAME"='KING')
索引全扫描
SQL>selectempnofromemp;14rowsselected.ExecutionPlan----------------------------------------------------------Planhashvalue:179099197---------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|---------------------------------------------------------------------------|0|SELECTSTATEMENT||14|56|1(0)|00:00:01||1|INDEXFULLSCAN|PK_EMP|14|56|1(0)|00:00:01|---------------------------------------------------------------------------
索引全扫描,并不是扫描全部的索引。它实际上只需扫描索引的叶子节点。但是为了找到叶子节点的位置,也会扫描部分的分支节点。
我们看如下查询
SQL>selectempno,enamefromemp;14rowsselected.ExecutionPlan----------------------------------------------------------Planhashvalue:3956160932--------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|--------------------------------------------------------------------------|0|SELECTSTATEMENT||14|140|3(0)|00:00:01||1|TABLEACCESSFULL|EMP|14|140|3(0)|00:00:01|--------------------------------------------------------------------------
查询列ename并不在索引中,所以走的是全表扫描。但是如果我们将语句做如下修改。
SQL>selectempno,enamefromemporderbyempno;14rowsselected.ExecutionPlan----------------------------------------------------------Planhashvalue:4170700152--------------------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|--------------------------------------------------------------------------------------|0|SELECTSTATEMENT||14|140|2(0)|00:00:01||1|TABLEACCESSBYINDEXROWID|EMP|14|140|2(0)|00:00:01||2|INDEXFULLSCAN|PK_EMP|14||1(0)|00:00:01|--------------------------------------------------------------------------------------
Oracle为了避免排序操作,而使用了索引全扫描。因为索引是有序的数据,并且索引全扫描是按顺序的单块读操作。
max和min
SQL>selectmax(empno)fromemp;ExecutionPlan----------------------------------------------------------Planhashvalue:1707959928-------------------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|-------------------------------------------------------------------------------------|0|SELECTSTATEMENT||1|4|1(0)|00:00:01||1|SORTAGGREGATE||1|4||||2|INDEXFULLSCAN(MIN/MAX)|PK_EMP|1|4|1(0)|00:00:01|
该扫描使用了索引全扫描,但其实并非真正的全扫描,max和min限定词使得Oracle知道何时停止,它只是扫描最高块或者最低块。
索引跳跃扫描
通常要使用索引,索引的前置列一定要出现在查询条件中。
SQL>createtablet(aint,bint,cint,dint,eint,fint,gint);SQL>createindext_idxont(a,b,c);
通常情况下只有如下的查询才会使用索引
select*fromtwherea=:a;select*fromtwherea=:aandb=:b;select*fromtwherea=:aandb=:bandc=:c;
但是如下查询不会使用索引(除了使用hint强制索引全扫描)
select*fromtwhereb=:b;select*fromtwherec=:c;select*fromtwhereb=:bandc=:c;
Oracle 9i后实现了跳跃索引扫描,条件如下:
谓词中使用了索引中其他的列。
前置列值的DISTINCT_NUM比较少。
我们看看如下示例
SQL>createtabletas2selectmod(rownum,3)a,rownumb,rownumc,object_named3fromall_objects;Tablecreated.SQL>createindext_idxont(a,b,c);Indexcreated.SQL>analyzetabletcomputestatistics;Tableanalyzed.SQL>select*fromtwhereb=1andc=1;ExecutionPlan----------------------------------------------------------Planhashvalue:2053318169-------------------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|-------------------------------------------------------------------------------------|0|SELECTSTATEMENT||1|34|5(0)|00:00:01||1|TABLEACCESSBYINDEXROWID|T|1|34|5(0)|00:00:01||*2|INDEXSKIPSCAN|T_IDX|1||4(0)|00:00:01|-------------------------------------------------------------------------------------PredicateInformation(identifiedbyoperationid):---------------------------------------------------2-access("B"=1AND"C"=1)filter("B"=1AND"C"=1)Statistics----------------------------------------------------------1recursivecalls0dbblockgets8consistentgets0physicalreads0redosize724bytessentviaSQL*Nettoclient523bytesreceivedviaSQL*Netfromclient2SQL*Netroundtripsto/fromclient0sorts(memory)0sorts(disk)1rowsprocessed
因为a的值比较少,只有3个,Oracle把索引(a,b,c) 看成3个小索引 。
索引快速全扫描
索引快速全扫描与索引全扫描明显的不同,它有如下特征
它读取索引中的每个块,包括所有分支块。
它采用多块读,像全表扫描一样。
它不按排序顺序扫描索引。
我们先建立一个表,并插入大量数据。
SQL>createtablebig_tableasselect*fromdba_objects;Tablecreated.SQL>insertintobig_tableselect*frombig_table;74577rowscreated.SQL>insertintobig_tableselect*frombig_table;223731rowscreated.SQL>/447462rowscreated.SQL>commit;Commitcomplete.SQL>altertablebig_tablemodifyobject_idnotnull;Tablealtered.SQL>createindexidx_big_table_objidonbig_table(object_id);Indexcreated.SQL>analyzetablebig_tablecomputestatistics;Tableanalyzed.
执行如下查询
SQL>setautottraceonlySQL>selectobject_idfrombig_table;894924rowsselected.ExecutionPlan----------------------------------------------------------Planhashvalue:205523069--------------------------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|--------------------------------------------------------------------------------------------|0|SELECTSTATEMENT||894K|3495K|544(2)|00:00:07||1|INDEXFASTFULLSCAN|IDX_BIG_TABLE_OBJID|894K|3495K|544(2)|00:00:07|--------------------------------------------------------------------------------------------Statistics----------------------------------------------------------15recursivecalls0dbblockgets61534consistentgets2physicalreads0redosize15755358bytessentviaSQL*Nettoclient656794bytesreceivedviaSQL*Netfromclient59663SQL*Netroundtripsto/fromclient0sorts(memory)0sorts(disk)894924rowsprocessed
查询使用的是索引快速全扫描。
有心的人可以思考一下,如下查询为啥没有使用索引快速全扫描,而使用了索引全扫描。
SQL>selectempnofromemp;14rowsselected.ExecutionPlan----------------------------------------------------------Planhashvalue:179099197---------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|---------------------------------------------------------------------------|0|SELECTSTATEMENT||14|56|1(0)|00:00:01||1|INDEXFULLSCAN|PK_EMP|14|56|1(0)|00:00:01|---------------------------------------------------------------------------Statistics----------------------------------------------------------0recursivecalls0dbblockgets2consistentgets0physicalreads0redosize686bytessentviaSQL*Nettoclient523bytesreceivedviaSQL*Netfromclient2SQL*Netroundtripsto/fromclient0sorts(memory)0sorts(disk)14rowsprocessed
索引连接
索引连接(index join)是在表中存在多个索引时针对某个查询所选中的索引路径。
我们看如下例子
SQL>createtablet1asselect*fromdba_objects;Tablecreated.SQL>createindext1_idx1ont1(object_id);Indexcreated.SQL>createindext1_idx2ont1(owner,object_type);Indexcreated.SQL>analyzetablet1computestatistics;Tableanalyzed.SQL>setautottraceonlySQL>selectobject_id,owner,object_typefromt12whereobject_idbetween100and20003andowner='SYS';1478rowsselected.ExecutionPlan----------------------------------------------------------Planhashvalue:2563395799---------------------------------------------------------------------------------------|Id|Operation|Name|Rows|Bytes|Cost(%CPU)|Time|---------------------------------------------------------------------------------------|0|SELECTSTATEMENT||69|1173|18(6)|00:00:01||*1|VIEW|index$_join$_001|69|1173|18(6)|00:00:01||*2|HASHJOIN|||||||*3|INDEXRANGESCAN|T1_IDX1|69|1173|7(15)|00:00:01||*4|INDEXRANGESCAN|T1_IDX2|69|1173|12(9)|00:00:01|---------------------------------------------------------------------------------------PredicateInformation(identifiedbyoperationid):---------------------------------------------------1-filter("OBJECT_ID"<=2000AND"OWNER"='SYS'AND"OBJECT_ID">=100)2-access(ROWID=ROWID)3-access("OBJECT_ID">=100AND"OBJECT_ID"<=2000)4-access("OWNER"='SYS')Statistics----------------------------------------------------------1recursivecalls0dbblockgets215consistentgets0physicalreads0redosize32014bytessentviaSQL*Nettoclient1601bytesreceivedviaSQL*Netfromclient100SQL*Netroundtripsto/fromclient0sorts(memory)0sorts(disk)1478rowsprocessed
优化器通过扫描T1_IDX1,T1_IDX2得到结果集,用两个结果集的rowid进行join运算,得到返回集。
这样避免扫描表。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。