oracle--如何分析执行计划
假设LARGE_TABLE是一个较大的表,且username列上没有索引,则运行下面的语句:
SQL>SELECT*FROMLARGE_TABLEwhereUSERNAME=‘TEST’;
QueryPlan
-----------------------------------------
SELECTSTATEMENTOptimizer=CHOOSE(Cost=1234Card=1Bytes=14)
TABLEACCESSFULLLARGE_TABLE[:Q65001][ANALYZED]
在这个例子中,TABLEACCESSFULLLARGE_TABLE是第一个操作,意思是在LARGE_TABLE表上做全表扫描。当这个操作完成之后,产生的rowsource中的数据被送往下一步骤进行处理,在此例中,SELECTSTATEMENT操作是这个查询语句的最后一步。
ptimizer=CHOOSE指明这个查询的optimizer_mode,即optimizer_mode初始化参数指定的值,它并不是指语句执行时真的使用了该优化器。决定该语句使用何种优化器的唯一方法是看后面的cost部分。例如,如果给出的是下面的形式,则表明使用的是CBO优化器,此处的cost表示优化器认为该执行计划的代价:
SELECTSTATEMENTOptimizer=CHOOSE(Cost=1234Card=1Bytes=14)
然而假如执行计划中给出的是类似下面的信息,则表明是使用RBO优化器,因为cost部分的值为空,或者压根就没有cost部分。
SELECTSTATEMENTOptimizer=CHOOSECost=
SELECTSTATEMENTOptimizer=CHOOSE
这样我们从Optimizer后面的信息中可以得出执行该语句时到底用了什么样的优化器。特别的,如果Optimizer=ALL_ROWS|FIRST_ROWS|FIRST_ROWS_n,则使用的是CBO优化器;如果Optimizer=RULE,则使用的是RBO优化器。
cost属性的值是一个在oracle内部用来比较各个执行计划所耗费的代价的值,从而使优化器可以选择最好的执行计划。不同语句的cost值不具有可比性,只能对同一个语句的不同执行计划的cost值进行比较。
[:Q65001]表明该部分查询是以并行方式运行的。里面的数据表示这个操作是由并行查询的一个slave进程处理的,以便该操作可以区别于串行执行的操作。
[ANALYZED]表明操作中引用的对象被分析过了,在数据字典中有该对象的统计信息可以供CBO使用。
例2:
假定A、B、C都是不是小表,且在A表上一个组合索引:A(a.col1,a.col2),注意a.col1列为索引的引导列。
考虑下面的查询:
selectA.col4
fromA,B,C
whereB.col3=10andA.col1=B.col1andA.col2=C.col2andC.col3=5
ExecutionPlan
----------------------------------------------------------
0SELECTSTATEMENTptimizer=CHOOSE
10MERGEJOIN
21SORT(JOIN)
32NESTEDLOOPS
43TABLEACCESS(FULL)OF'B'
53TABLEACCESS(BYINDEXROWID)OF'A'
65INDEX(RANGESCAN)OF'INX_COL12A'(NON-UNIQUE)
71SORT(JOIN)
87TABLEACCESS(FULL)OF'C'
Statistics
----------------------------------------------------------
0recursivecalls
8dbblockgets
6consistentgets
0physicalreads
0redosize
551bytessentviaSQL*Nettoclient
430bytesreceivedviaSQL*Netfromclient
2SQL*Netroundtripsto/fromclient
2sorts(memory)
0sorts(disk)
6rowsprocessed
在表做连接时,只能2个表先做连接,然后将连接后的结果作为一个rowsource,与剩下的表做连接,在上面的例子中,连接顺序为B与A先连接,然后再与C连接:
B<--->A<--->C
col3=10col3=5
如果没有执行计划,分析一下,上面的3个表应该拿哪一个作为第一个驱动表?从SQL语句看来,只有B表与C表上有限制条件,所以第一个驱动表应该为这2个表中的一个,到底是哪一个呢?
B表有谓词B.col3=10,这样在对B表做全表扫描的时候就将where子句中的限制条件(B.col3=10)用上,从而得到一个较小的rowsource,所以B表应该作为第一个驱动表。而且这样的话,如果再与A表做关联,可以有效利用A表的索引(因为A表的col1列为leadingcolumn)。
当然上面的查询中C表上也有谓词(C.col3=5),有人可能认为C表作为第一个驱动表也能获得较好的性能。让我们再来分析一下:如果C表作为第一个驱动表,则能保证驱动表生成很小的rowsource,但是看看连接条件A.col2=C.col2,此时就没有机会利用A表的索引,因为A表的col2列不为leadingcolumn,这样nestedloop的效率很差,从而导致查询的效率很差。所以对于NL连接选择正确的驱动表很重要。
因此上面查询比较好的连接顺序为(B-->A)-->C。如果数据库是基于代价的优化器,它会利用计算出的代价来决定合适的驱动表与合适的连接顺序。一般来说,CBO都会选择正确的连接顺序,如果CBO选择了比较差的连接顺序,我们还可以使用ORACLE提供的hints来让CBO采用正确的连接顺序。如下所示:
select/*+ordered*/A.col4
fromB,A,C
whereB.col3=10
andA.col1=B.col1
andA.col2=C.col2
andC.col3=5
既然选择正确的驱动表这么重要,那么让我们来看一下执行计划,到底各个表之间是如何关联的,从而得到执行计划中哪个表应该为驱动表:
在执行计划中,需要知道哪个操作是先执行的,哪个操作是后执行的,这对于判断哪个表为驱动表有用处。判断之前,如果对表的访问是通过rowid,且该rowid的值是从索引扫描中得来得,则将该索引扫描先从执行计划中暂时去掉。然后在执行计划剩下的部分中,判断执行顺序的指导原则就是:最右、最上的操作先执行。具体解释如下:
得到去除妨碍判断的索引扫描后的执行计划:
ExecutionPlan
----------------------------------------------------------
0SELECTSTATEMENTptimizer=CHOOSE
10MERGEJOIN
21SORT(JOIN)
32NESTEDLOOPS
43TABLEACCESS(FULL)OF'B'
53TABLEACCESS(BYINDEXROWID)OF'A'
71SORT(JOIN)
87TABLEACCESS(FULL)OF'C'
看执行计划的第3列,即字母部分,每列值的左面有空格作为缩进字符。在该列值左边的空格越多,说明该列值的缩进越多,该列值也越靠右。如上面的执行计划所示:第一列值为6的行的缩进最多,即该行最靠右;第一列值为4、5的行的缩进一样,其靠右的程度也一样,但是第一列值为4的行比第一列值为5的行靠上;谈论上下关系时,只对连续的、缩进一致的行有效。
从这个图中我们可以看到,对于NESTEDLOOPS部分,最右、最上的操作是TABLEACCESS(FULL)OF'B',所以这一操作先执行,所以该操作对应的B表为第一个驱动表(外部表),自然,A表就为内部表了。从图中还可以看出,B与A表做嵌套循环后生成了新的rowsource,对该rowsource进行来排序后,与C表对应的排序了的rowsource(应用了C.col3=5限制条件)进行MSJ连接操作。所以从上面可以得出如下事实:B表先与A表做嵌套循环,然后将生成的rowsource与C表做排序—合并连接。
通过分析上面的执行计划,我们不能说C表一定在B、A表之后才被读取,事实上,B表有可能与C表同时被读入内存,因为将表中的数据读入内存的操作可能为并行的。事实上许多操作可能为交叉进行的,因为ORACLE读取数据时,如果就是需要一行数据也是将该行所在的整个数据块读入内存,而且还有可能为多块读。
看执行计划时,我们的关键不是看哪个操作先执行,哪个操作后执行,而是关键看表之间连接的顺序(如得知哪个为驱动表,这需要从操作的顺序进行判断)、使用了何种类型的关联及具体的存取路径(如判断是否利用了索引)
在从执行计划中判断出哪个表为驱动表后,根据我们的知识判断该表作为驱动表(就像上面判断ABC表那样)是否合适,如果不合适,对SQL语句进行更改,使优化器可以选择正确的驱动表。
对于RBO优化器:
在ORACLE文档上说:对于RBO来说,以from子句中从右到左的顺序选择驱动表,即最右边的表为第一个驱动表,这是其英文原文:AllthingsbeingequalRBOchoosesthedrivingorderbytakingthetablesintheFROMclauseRIGHTtoLEFT。不过,在我做的测试中,从来也没有验证过这种说法是正确的。我认为,即使在RBO中,也是有一套规则来决定使用哪种连接类型和哪个表作为驱动表,在选择时肯定会考虑当前索引的情况,还可能会考虑where中的限制条件,但是肯定是与where中限制条件的位置无关。
测试:
如果我创建3个表:
createtableA(col1number(4,0),col2number(4,0),col4char(30));
createtableB(col1number(4,0),col3number(4,0),name_bchar(30));
createtableC(col2number(4,0),col3number(4,0),name_cchar(30));
createindexinx_col12Aona(col1,col2);
执行查询:
selectA.col4
fromB,A,C
whereB.col3=10
andA.col1=B.col1
andA.col2=C.col2
andC.col3=5;
ExecutionPlan
----------------------------------------------------------
0SELECTSTATEMENTptimizer=RULE
10MERGEJOIN
21SORT(JOIN)
32NESTEDLOOPS
43TABLEACCESS(FULL)OF'B'
53TABLEACCESS(BYINDEXROWID)OF'A'
65INDEX(RANGESCAN)OF'INX_COL12A'(NON-UNIQUE)
71SORT(JOIN)
87TABLEACCESS(FULL)OF'C'
selectA.col4
fromB,A,C
whereA.col1=B.col1
andA.col2=C.col2;
ExecutionPlan
----------------------------------------------------------
0SELECTSTATEMENTptimizer=RULE
10MERGEJOIN
21SORT(JOIN)
32NESTEDLOOPS
43TABLEACCESS(FULL)OF'B'
53TABLEACCESS(BYINDEXROWID)OF'A'
65INDEX(RANGESCAN)OF'INX_COL12A'(NON-UNIQUE)
71SORT(JOIN)
87TABLEACCESS(FULL)OF'C'
将A表上的索引inx_col12A删除后:
selectA.col4
fromB,A,C
whereA.col1=B.col1
andA.col2=C.col2;
ExecutionPlan
----------------------------------------------------------
0SELECTSTATEMENTptimizer=RULE
10MERGEJOIN
21SORT(JOIN)
32MERGEJOIN
43SORT(JOIN)
54TABLEACCESS(FULL)OF'C'
63SORT(JOIN)
76TABLEACCESS(FULL)OF'A'
81SORT(JOIN)
98TABLEACCESS(FULL)OF'B'
通过上面的这些例子,使我对oracle文档上的”AllthingsbeingequalRBOchoosesthedrivingorderbytakingthetablesintheFROMclauseRIGHTtoLEFT”这句话持怀疑态度。此时,我也不能使用hints来强制优化器使用nestedloop,如果使用了hints,这样就自动使用CBO优化器,而不是RBO优化器了。
对于CBO优化器:
CBO根据统计信息选择驱动表,假如没有统计信息,则在from子句中从左到右的顺序选择驱动表。这与RBO选择的顺序正好相反。这是英文原文(CBOdeterminesjoinorderfromcostsderivedfromgatheredstatistics.IftherearenostatsthenCBOchoosesthedrivingorderoftablesfromLEFTtoRIGHTintheFROMclause.ThisisOPPOSITEtotheRBO)。我还是没法证实这句话的正确性。不过经过验证:“如果用ordered提示(此时肯定用CBO),则以from子句中按从左到右的顺序选择驱动表”这句话是正确的。实际上在CBO中,如果有统计数据(即对表与索引进行了分析),则优化器会自动根据cost值决定采用哪种连接类型,并选择合适的驱动表,这与where子句中各个限制条件的位置没有任何关系。如果我们要改变优化器选择的连接类型或驱动表,则就需要使用hints了,具体hints的用法在后面会给予介绍。
测试:
如果我创建的3个表:
createtableA(col1number(4,0),col2number(4,0),col4char(30));
createtableB(col1number(4,0),col3number(4,0),name_bchar(30));
createtableC(col2number(4,0),col3number(4,0),name_cchar(30));
createindexinx_col12Aona(col1,col2);
执行查询:
selectA.col4
fromB,A,C
whereB.col3=10
andA.col1=B.col1
andA.col2=C.col2
andC.col3=5;
ExecutionPlan
----------------------------------------------------------
0SELECTSTATEMENTptimizer=ALL_ROWS(Cost=3Card=1Bytes=110)
10NESTEDLOOPS(Cost=3Card=1Bytes=110)
21MERGEJOIN(CARTESIAN)(Cost=2Card=1Bytes=52)
32TABLEACCESS(FULL)OF'B'(Cost=1Card=1Bytes=26)
42SORT(JOIN)(Cost=1Card=1Bytes=26)
54TABLEACCESS(FULL)OF'C'(Cost=1Card=1Bytes=26)
61TABLEACCESS(FULL)OF'A'(Cost=1Card=82Bytes=4756)
selectA.col4
fromB,A,C
whereA.col1=B.col1
andA.col2=C.col2;
ExecutionPlan
----------------------------------------------------------
0SELECTSTATEMENTptimizer=ALL_ROWS(Cost=5Card=55Bytes=4620)
10HASHJOIN(Cost=5Card=55Bytes=4620)
21HASHJOIN(Cost=3Card=67Bytes=4757)
32TABLEACCESS(FULL)OF'B'(Cost=1Card=82Bytes=1066)
42TABLEACCESS(FULL)OF'A'(Cost=1Card=82Bytes=4756)
51TABLEACCESS(FULL)OF'C'(Cost=1Card=82Bytes=1066)
将A表上的索引inx_col12A删除后:
selectA.col4
fromB,A,C
whereA.col1=B.col1
andA.col2=C.col2;
ExecutionPlan
----------------------------------------------------------
0SELECTSTATEMENTptimizer=ALL_ROWS(Cost=5Card=55Bytes=4620)
10HASHJOIN(Cost=5Card=55Bytes=4620)
21HASHJOIN(Cost=3Card=67Bytes=4757)
32TABLEACCESS(FULL)OF'B'(Cost=1Card=82Bytes=1066)
42TABLEACCESS(FULL)OF'A'(Cost=1Card=82Bytes=4756)
51TABLEACCESS(FULL)OF'C'(Cost=1Card=82Bytes=1066)
select/*+ORDERED*/A.col4
fromC,A,B
whereB.col3=10
andA.col1=B.col1
andA.col2=C.col2
andC.col3=5;
ExecutionPlan
----------------------------------------------------------
0SELECTSTATEMENTptimizer=ALL_ROWS(Cost=3Card=1Bytes=110)
10NESTEDLOOPS(Cost=3Card=1Bytes=110)
21NESTEDLOOPS(Cost=2Card=1Bytes=84)
32TABLEACCESS(FULL)OF'C'(Cost=1Card=1Bytes=26)
42TABLEACCESS(FULL)OF'A'(Cost=1Card=82Bytes=4756)
51TABLEACCESS(FULL)OF'B'(Cost=1Card=1Bytes=26)
这个查询验证了通过ORDERED提示可以正确的提示优化器选择哪个表作为优化器。