You are on page 1of 6

Ok, tell me if this helps.

It is an excerpt from my forth coming book:

1.3.3 Reading an Explain Plan This is a frequently asked question - how exactly do you read an explain plan. Here I will present my 'simple' approach to reading the plan. I do suggest however that a quick read of the chapter in the Oracle Performance Guide - the chapter on the explain plan command - would be very useful as well. For all of the details on reading an explain plan, please refer to the Oracle Performance Guide. There are complete details on how to read the query plan and interpret the results. We'll take a look at a query plan resulting from a query against the SCOTT/TIGER tables (note, I add primary keys to the EMP and DEPT tables - hence, they are indexed): scott@ORA920> delete from plan_table; 7 rows deleted. scott@ORA920> explain plan for 2 select ename, dname, grade 3 from emp, dept, salgrade 4 where emp.deptno = dept.deptno 5 and emp.sal between salgrade.losal and salgrade.hisal 6 / Explained. scott@ORA920> @?/rdbms/admin/utlxpls PLAN_TABLE_OUTPUT ----------------------------------------------------------------| Id | Operation |Name |Rows|Bytes|Cost | ----------------------------------------------------------------| 0 | SELECT STATEMENT | | | | | | 1 | NESTED LOOPS | | | | | | 2 | NESTED LOOPS | | | | | | 3 | TABLE ACCESS FULL | SALGRADE| | | | |* 4 | TABLE ACCESS FULL | EMP | | | | | 5 | TABLE ACCESS BY INDEX ROWID| DEPT | | | | |* 6 | INDEX UNIQUE SCAN | DEPT_PK | | | | ----------------------------------------------------------------Predicate Information (identified by operation id): --------------------------------------------------4 - filter("EMP"."SAL"<="SALGRADE"."HISAL" AND "EMP"."SAL">="SALGRADE"."LOSAL") 6 - access("EMP"."DEPTNO"="DEPT"."DEPTNO") Note: rule based optimization 21 rows selected.

Now - what happens first? How does that plan actually get evaluated? First I'll show you the psuedo code for how the plan is evaluated and then we'll discuss how I arrived at this conclusion: For salgrade in (select * from salgrade) Loop For emp in ( select * from emp ) Loop If ( emp.sal between salgrade.losal and salgrade.hisal ) Then Select * into dept_rec From dept Where dept.deptno = emp.deptno; OUTPUT RECORD with fields from salgrade,emp,dept End if; End loop; End loop; The way I read the plan was to turn it into a graph of sorts - an evaluation tree. In order to do that, we need to understand something about access paths. For detailed information on the access paths available to Oracle, please see the Oracle Performance and Tuning Guide. In order to build the tree - we can start at the top, with step 1. That will be our "root node" in the tree. Next, we need to find the things that "feed" this root node - that will be steps 2 and 5 - as you can see - 2 and 5 are at the same level of indentation - they "feed" into step 1. Further, we can see that steps 3 and 4 feed step 2 and that step 6 feeds step 5. Putting that together iteratively - we would draw: 1 / \ 4

2 / \

5 \ 6

And then just read the tree. In order to get 1 we need 2 and 5 - 2 is "first". In order to get 2, we need 3 and 4. 3 is "first". That is how I arrived at the psuedo code for: For salgrade in (select * from salgrade) Loop For emp in ( select * from emp ) Loop Full scan SALGRADE is step 3, full scan EMP is step 4 and step 2 is a nested loop - which is roughly equivalent to two "for loops". Once we get step 2 going like that - we can look at step 5. Step 5 runs step 6 first - step 6 is the index scan step. We are taking the output of step 2 here and using that to "feed" this part of the query plan. So, the output from step 2 is used to perform an index scan - that index scan output is used to TABLE ACCESS BY ROWID the DEPT table and that result is the output of step 1 - our result set.

Now, to make this "interesting", we will run an equivalent query - but we'll mix up the order of the tables in the from clause this time. Since I am using the rule based optimizer - this will affect the generated query plan (and is just one reason why you DON'T want to use the rule based optimizer! We'll cover more reasons in a later section). We'll use the same logic to build its query plan tree and evaluate how it processed the query: scott@ORA920> delete from plan_table; 7 rows deleted. scott@ORA920> explain plan for 2 select ename, dname, grade 3 from salgrade, dept, emp 4 where emp.deptno = dept.deptno 5 and emp.sal between salgrade.losal and salgrade.hisal 6 / Explained. scott@ORA920> @?/rdbms/admin/utlxpls PLAN_TABLE_OUTPUT ------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes ------------------------------------------------------------------| 0 | SELECT STATEMENT | | | | 1 | NESTED LOOPS | | | | 2 | NESTED LOOPS | | | | 3 | TABLE ACCESS FULL | EMP | | | 4 | TABLE ACCESS BY INDEX ROWID| DEPT | | |* 5 | INDEX UNIQUE SCAN | DEPT_PK | | |* 6 | TABLE ACCESS FULL | SALGRADE | | ------------------------------------------------------------------Predicate Information (identified by operation id): --------------------------------------------------5 - access("EMP"."DEPTNO"="DEPT"."DEPTNO") 6 - filter("EMP"."SAL"<="SALGRADE"."HISAL" AND "EMP"."SAL">="SALGRADE"."LOSAL") Note: rule based optimization 21 rows selected. Here we see that steps 2 and 6 feed 1, steps 3 and 4 feed 2, and step 5 feeds 4. Drawing the tree: 1 / \ 2 6 / \ 3 4 \ 5 So, the psuedo code logic here is - starting with steps 3 and 4: For emp in ( select * from emp ) Loop -- using the index

Select * from dept where dept.deptno = emp.deptno For salgrade in (select * from salgrade ) Loop If ( emp.sal between salgrade.losal and salgrade.hisal ) Then OUTPUT RECORD; End if; End loop End loop; And that is it - if you draw the graphical tree like that and then read it bottom up, left to right, you'll get a good understanding of the "flow" of the data.

Fabulous! January 17, 2003 - 6pm US/Eastern Bookmark | Bottom | Top Reviewer: Kashif from Houston, TX
Hi Tom, Thanks for that excellent easy-to-follow explanation. I understand the plan much better now. I'm just not quite getting my particular plan: batchcode@QGF1> get ins2cpi.sql 1 insert into hmc.cpi_lnum (lnum, clnum, Loan_Type, Loan_amount, 2 RESEND_FLAG, PRVS_LOAD, Error_flag) 3 ( 4 select /*+ ORDERED PUSH_SUBQ INDEX (dt pk_dates) */ 5 dt.lnum, tr.clnum, pr.s_ltype, pr.ola , 'F', 1, 'N' 6 from chk_screen c, 7 hmc_dates hmc, 8 dates dt, 9 hmv_current_status hmv, 10 tracking tr, 11 product pr 12 where c.lnum = hmc.lnum (+) 13 and c.lnum = dt.lnum 14 and c.lnum = hmv.lnum 15 and c.lnum = tr.lnum 16 and c.lnum = pr.lnum 17 and dt.lnfundat >= to_date ( '10111999', 'MMDDYYYY' ) 18 and dt.lnfundat <= to_date ( '12182002', 'MMDDYYYY' ) 19 and hmc.send_to_cpi is null 20 and c.s_checkstatus = 'CS_CMPT' 21 and hmv.status_code||'' >= 700 22 and hmv.status_code||'' <= 2300 23 and length (tr.clnum) >= 10 24 and not exists (select null 25 from wireinfo w 26 where w.lnum = c.lnum) 27* ) batchcode@QGF1> get queryplan.txt ID Query_Plan ---------- ------------------------------------------------------0 INSERT STATEMENT Cost = 71

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

NESTED LOOPS NESTED LOOPS NESTED LOOPS NESTED LOOPS FILTER NESTED LOOPS OUTER TABLE ACCESS FULL CHK_SCREEN INDEX RANGE SCAN PK_WIREINFO TABLE ACCESS BY INDEX ROWID HMC_DATES INDEX UNIQUE SCAN HMC_DATES TABLE ACCESS BY INDEX ROWID DATES INDEX UNIQUE SCAN PK_DATES TABLE ACCESS BY INDEX ROWID HMV_CURRENT_STATUS INDEX UNIQUE SCAN PK_HMV_CURRENT_STATUS TABLE ACCESS BY INDEX ROWID TRACKING INDEX UNIQUE SCAN PK_TRACKING TABLE ACCESS BY INDEX ROWID PRODUCT INDEX UNIQUE SCAN PK_PRODUCT

19 rows selected. batchcode@QGF1> It all makes sense to me, except for step 8, where there is a range scan on the PK_WIREINFO index. From what you explained, this step is feeding step 7, which is the FTS of chk_screen. But that seems counter-intuitive. How can there be a range scan on pk_wireinfo, and it also be the starting point for the plan? Alternatively, if I think that chk_screen was full table scanned first, followed by a range scan on pk_wireinfo for the ids produced from the FTS of chk_screen, that makes complete intuitive sense to me, but then it fails your explanation. I know this is caused by the PUSH_SUBQ hint used, which is causing the subquery to be evaluated first. Any feedback would be helpful. Thanks in advance. Kashif

Followup January 17, 2003 - 6pm US/Eastern:


not knowing the tables and what indexes go where -- well -- that makes it sort of difficult. (you know my opinion of hints too don't you?)

For anyone interested... January 28, 2003 - 1pm US/Eastern Bookmark | Bottom | Top Reviewer: Kashif from Houston, TX

Thanks Tom, for the explanation. For anyone interested, Chapter 9 of the Performance Tuning Guide for 9i, "Using EXPLAIN PLAN", is significantly better than previous releases (pre-9i), especially when it comes to explaining how to read the EXPLAIN PLAN in the section, "Reading EXPLAIN PLAN Output". This coupled with Tom's explanation above provided me with a comprehensive understanding of the EXPLAIN PLAN. Keep up the good work Tom. Kashif

You might also like