Jignesh Kariya
 Current page : Home      Oracle Articles
Reading an Explain Plan

For full thread visit http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:231814117467

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
/
2 5
/
3 4 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.