Oracle SQL Plan Execution
Oracle SQL Plan Execution
really works
Tanel Põder
http://www.tanelpoder.com
What is an execution plan?
For Oracle server:
Parsed, optimized and compiled SQL code kept inside library cache
Tanel Põder
One slide for getting execution plan
Starting from 9.2 the usual way is:
explain plan for <statement>
select * from table(dbms_xplan.display)
In 10g
the autotrace also uses dbms_xplan
set autotrace on
or select * from table(dbms_xplan.display_cursor())
Explain plan for has problems:
In 11g 1) It treats all bind variables as
DBMS_SQLTUNE.REPORT_SQL_MONITOR VARCHAR2
2) It might not show you the real
Other methods exec plan used!
sql_trace / 10046 trace + tkprof utility
Use V$SQL_PLAN_STATISTICS /
v$sql_plan dbms_xplan.display_cursor
setting event 10132 at level 1 instead!
Tanel Põder
Parse stages
Syntactic check
Syntax, keywords, sanity
soft parse
Semantic check
Whether objects referenced exist, are accessible (by
permissions) and are usable
View merging
Queries are written to reference base tables
Can merge both stored views and inline views
hard parse
Query transformation
Transitivity, etc (example: if a=1 and a=b then b=1)
Optimization
Query execution plan (QEP) generation
Loading SQL and execution plan in library cache
Tanel Põder
SQL execution basics
------------------------------------------
Execution plan
| Id | Operation | Name |
------------------------------------------
| 0 | SELECT STATEMENT | | SELECT
|* 1 | HASH JOIN | | cursor
|* 2 | TABLE ACCESS FULL| DEPARTMENTS |
processor
|* 3 | TABLE ACCESS FULL| EMPLOYEES |
------------------------------------------ application
row source
SELECT Query
E.LAST_NAME,
D.DEPARTMENT_NAME
FROM
EMPLOYEES E,
DEPARTMENTS D
WHERE
E.DEPARTMENT_ID =
D.DEPARTMENT_ID HASH JOIN
FI
LT
AND D.DEPARTMENT_NAME =
ER
ta
ER
'Sales'
bl
LT
AND E.SALARY > 2000; e
ce r o ac
FI
r w ce
an u
sc so so s s
ur fu
le w ce l l
ta
b ro
DEPARTMENTS EMPLOYEES
Tanel Põder
SQL execution basics - multitable joins
SELECT Multiple joins
E.LAST_NAME,
D.DEPARTMENT_NAME,
SELECT
L.CITY cursor
FROM processor
EMPLOYEES E, app.
row source
DEPARTMENTS D,
LOCATIONS L
WHERE
E.DEPARTMENT_ID = D.DEPARTMENT_ID
AND D.DEPARTMENT_NAME = 'Sales'
AND D.LOCATION_ID = L.LOCATION_IID
AND E.SALARY > 2000;
HASH JOIN ro
e w
u rc so
Only two row sources can be so u
joined together at a time w rc
ro e
DEPARTMENTS LOCATIONS
Tanel Põder
SQL execution terminology
ACCESS PATH
A means to access physical data in database storage
From tables, indexes, external tables, database links
ROW SOURCE
A virtual stream of rows
Can come through access paths from tables, indexes
Or from other child row sources
FILTER PREDICATE
A property of row source - can discard rows based on defined
conditions - filter predicates
JOIN
Filters and merges rows based on matching rows from child
rowsources. Matching is defined by join predicates
Any join operator can join only two inputs
Tanel Põder
First rule for reading an execution plan
Parent operations get input only from their children
-------------------------------------------------------------------------
| Id | Operation Execution plan
| Name |
0 | SELECT STATEMENT
structure
-------------------------------------------------------------------------
| | |
|* 1 | FILTER | |
| 2 | NESTED LOOPS OUTER | |
|* 3 | HASH JOIN OUTER | |
| 4 | NESTED LOOPS OUTER | |
| 5 | NESTED LOOPS OUTER | |
|* 6 | HASH JOIN | |
| 7 | TABLE ACCESS FULL | USER$ |
| 8 | NESTED LOOPS | |
|* 9 | HASH JOIN | |
| 10 | MERGE JOIN CARTESIAN | |
|* 11 | HASH JOIN | |
|* 12 | FIXED TABLE FULL | X$KSPPI |
| 13 | FIXED TABLE FULL | X$KSPPCV |
| 14 | BUFFER SORT | |
| 15 | TABLE ACCESS FULL | TS$ |
|* 16 | TABLE ACCESS FULL | TAB$ |
|* 17 | TABLE ACCESS BY INDEX ROWID | OBJ$ |
|* 18 | INDEX UNIQUE SCAN | I_OBJ1 |
| 19 | TABLE ACCESS BY INDEX ROWID | OBJ$ |
|* 20 | INDEX UNIQUE SCAN | I_OBJ1 |
| 21 | TABLE ACCESS BY INDEX ROWID | OBJ$ |
|* 22 | INDEX UNIQUE SCAN | I_OBJ1 |
| 23 | TABLE ACCESS FULL | USER$ |
| 24 | TABLE ACCESS CLUSTER | SEG$ |
|* 25 | INDEX UNIQUE SCAN | I_FILE#_BLOCK# |
| 26 | NESTED LOOPS | |
|* 27 | INDEX RANGE SCAN | I_OBJAUTH1 |
|* 28 | FIXED TABLE FULL | X$KZSRO |
|* 29 | FIXED TABLE FULL | X$KZSPR |
-------------------------------------------------------------------------
Tanel Põder
Second rule for reading an execution plan
Data access starts from the first line without children
-------------------------------------------------------------------------
| Id | Operation Execution plan
| Name |
0 | SELECT STATEMENT
structure
-------------------------------------------------------------------------
| | |
|* 1 | FILTER | |
| 2 | NESTED LOOPS OUTER | |
|* 3 | HASH JOIN OUTER | |
| 4 | NESTED LOOPS OUTER | |
| 5 | NESTED LOOPS OUTER | |
|* 6 | HASH JOIN | |
| 7 | TABLE ACCESS FULL | USER$ |
| 8 | NESTED LOOPS | |
First operation with no children
|* 9 | HASH JOIN | | (leaf operation) accesses data
| 10 | MERGE JOIN CARTESIAN | |
|* 11 | HASH JOIN | |
|* 12 | FIXED TABLE FULL | X$KSPPI |
| 13 | FIXED TABLE FULL | X$KSPPCV |
| 14 | BUFFER SORT | |
| 15 | TABLE ACCESS FULL | TS$ |
|* 16 | TABLE ACCESS FULL | TAB$ |
|* 17 | TABLE ACCESS BY INDEX ROWID | OBJ$ |
|* 18 | INDEX UNIQUE SCAN | I_OBJ1 |
| 19 | TABLE ACCESS BY INDEX ROWID | OBJ$ |
|* 20 | INDEX UNIQUE SCAN | I_OBJ1 |
| 21 | TABLE ACCESS BY INDEX ROWID | OBJ$ |
|* 22 | INDEX UNIQUE SCAN | I_OBJ1 |
| 23 | TABLE ACCESS FULL | USER$ |
| 24 | TABLE ACCESS CLUSTER | SEG$ |
|* 25 | INDEX UNIQUE SCAN | I_FILE#_BLOCK# |
| 26 | NESTED LOOPS | |
|* 27 | INDEX RANGE SCAN | I_OBJAUTH1 |
|* 28 | FIXED TABLE FULL | X$KZSRO |
|* 29 | FIXED TABLE FULL | X$KZSPR |
-------------------------------------------------------------------------
Tanel Põder
Cascading rowsources
Data access starts from the first line without children
-------------------------------------------------------------------------
| Id | Operation Execution plan
| Name | SELECT
cursor
| 0 | SELECT STATEMENT |
structure
-------------------------------------------------------------------------
|
processor
app.
row source
|* 1 | FILTER | |
| 2 | NESTED LOOPS OUTER | |
|* 3 | HASH JOIN OUTER | |
| 4 | NESTED LOOPS OUTER | |
| 5 | NESTED LOOPS OUTER | | HASH
|* 6 | HASH JOIN | | JOIN
ce ro
| 7 | TABLE ACCESS FULL | USER$ | our w
so
s ur
| 8 | NESTED LOOPS | | w ce
ro
|* 9 | HASH JOIN | |
| 10 | MERGE JOIN CARTESIAN | | USER$ NL
|* 11 | HASH JOIN | | JOIN
|* 12 | FIXED TABLE FULL | X$KSPPI | ro
ce w
| 13 | FIXED TABLE FULL | X$KSPPCV | ur so
so ur
| 14 | BUFFER SORT | | w ce
ro
| 15 | TABLE ACCESS FULL | TS$ |
|* 16 | TABLE ACCESS FULL | TAB$ | OBJ$
HASH
|* 17 | TABLE ACCESS BY INDEX ROWID | OBJ$ | JOIN
|* 18 | INDEX UNIQUE SCAN | I_OBJ1 |
RS
| 19 | TABLE ACCESS BY INDEX ROWID | OBJ$ | S
R
|* 20 | INDEX UNIQUE SCAN | I_OBJ1 |
| 21 | TABLE ACCESS BY INDEX ROWID | OBJ$ | MERGE
TAB$
CART.
|* 22 | INDEX UNIQUE SCAN | I_OBJ1 | JOIN
| 23 | TABLE ACCESS FULL | USER$ | S
RS
| 24 | TABLE ACCESS CLUSTER | SEG$ | R
|* 25 | INDEX UNIQUE SCAN | I_FILE#_BLOCK# | Buffer
HASH
| 26 | NESTED LOOPS | | JOIN
SORT
RS
|* 27 | INDEX RANGE SCAN | I_OBJAUTH1 | S RS
|* 28 | FIXED TABLE FULL | X$KZSRO |R
|* 29 | FIXED TABLE FULL | X$KZSPR | TS$
X$KSPPI
------------------------------------------------------------------------- X$KSPPCV
Tanel Põder
SQL execution plan recap
Execution plan lines are just Oracle kernel functions!
In other words, each row source is a function
A
6
7
Estimated rows
TABLE ACCESS
HASH JOIN
FULL CBO rowcount estimate
USER$ .10
214.32
68
1690
68
1688
1
1
8 NESTED LOOPS OUTER 55.61 1690 1691 1
A 9 HASH JOIN RIGHT OUTER 48.82 1690 1691 1
10 Real
TABLE ACCESS# rows
FULL RealSEG$
measured rowcount
5.08 from5044
rowsource 5041 1
A 11 HASH JOIN 23.67 1690 1691 1
12 MERGE JOIN CARTESIAN 7.00 12 12 1
A 13 Op.JOIN
HASH iterations Number of times the6.70 rowsource fetch
1 was 1initialized1
F 14 FIXED TABLE FULL X$KSPPI .96 1 1 1
15 FIXED TABLE FULL X$KSPPCV 1.45 100 1440 1
16 Logical
BUFFER SORT reads Consistent buffer gets .28 12 12 1
17 TABLE ACCESS FULL TS$ .20 12 12 1
F 18 TABLE ACCESS FULL TAB$ 6.56 1690 1691 1
A 19 INDEXLogical writes
UNIQUE SCAN Current mode buffer gets (Note that some CUR gets
I_OBJ1 3.40 1 92 1691
F 20 TABLE ACCESS FULL OBJ$ 53.54 53517 53510 1
21 TABLE ACCESS FULL may not
OBJ$ always be due
.04 writing...)
53517 53514 1
Ch Op
ld ID Physical(identified
Predicate Information reads by operation
Physial id):
reads done by the rowsource function
--- ------ ---------------------------------------------------------------------------------------
0 2 Physical writes
- access("CX"."OWNER#"="CU"."USER#") Physical writes done by the rowsource function
4 - access("T"."DATAOBJ#"="CX"."OBJ#")
5 - access("O"."OWNER#"="U"."USER#")
7 Optimizer cost
- access("O"."OBJ#"="T"."OBJ#") Least significant thing for measuring the real
9
execution efficiency of a statement
- access("T"."FILE#"="S"."FILE#" AND "T"."BLOCK#"="S"."BLOCK#" AND "T"."TS#"="S"."TS#")
Tanel Poder
Põder
Advanced Troubleshooting - Getting stack traces
OS stack dumper
pstack - Solaris, Linux, HP-UX
procstack - AIX
gdb bt, mdb $c
Procwatcher (Metalink note: 459694.1)
Windows
windbg, procexp - but no symbolic function names in oracle.exe :(
Oracle internal
oradebug short_stack
oradebug dump errorstack
alter session set events '942 trace name errorstack'
Tanel Poder
Põder
Advanced - Interpreting rowsource functions with os_explain
select /*+ ordered use_nl(b) use_nl(c) use_nl(d)
full(a) full(b) full(c) full(d) */
count(*)
from sys.obj$ a, sys.obj$ b, sys.obj$ c, sys.obj$ d
where
a.owner# = b.owner# and b.owner# = c.owner#
and c.owner# = d.owner# and rownum <= 10000000000
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
Plan hash value: 4080710170
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 518 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| EMP | 14 | 518 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Tanel Põder
Full table scan with a filter predicate
Filter operation throws away non-matching rows
By definition, not the most efficient operation
Filter conditions can be seen in predicate section
SQL> select * from emp where ename = 'KING';
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------
Plan hash value: 4080710170
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 37 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EMP | 1 | 37 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Tanel Põder
Simple B*tree index+table access
Index tree is walked from root to leaf
Key values and ROWIDs are gotten from index
Table rows are gotten using ROWIDs
Access operator fetches only matching rows
• As opposed to filter which filters through the whole child rowsource
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 37 | 1 (0)|
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 37 | 1 (0)|
|* 2 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)|
---------------------------------------------------------------------------
Tanel Põder
Predicate attributes
Predicate = access
A means to avoid processing (some) unneeded data at all
Predicate = filter
Everything from child row source is processed / filtered
The non-matching rows are thrown away
SQL> select * from emp
2 where empno > 7000
3 and ename like 'KING%';
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 27 | 3 (0)|
|* 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 27 | 3 (0)|
|* 2 | INDEX RANGE SCAN | PK_EMP | 9 | | 2 (0)|
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------
1 - filter("ENAME" LIKE 'KING%')
2 - access("EMPNO">7000)
Tanel Põder
Index fast full scan
Doesn't necessarily return keys in order
The whole index segment is just scanned as Oracle finds its blocks on
disk (in contrast to tree walking)
Multiblock reads are used
As indexes don't usually contain all columns that tables do, FFS is
more efficient if all used columns are in index
Used mainly for aggregate functions, min/avg/sum,etc
Optimizer must know that all table rows are represented in index!
(null values and count example)
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 25 (0)|
| 1 | SORT AGGREGATE | | 1 | 5 | |
| 2 | INDEX FAST FULL SCAN| PK_EMP | 54121 | 264K| 25 (0)|
---------------------------------------------------------------------
Tanel Põder
Nested Loop Join
Nested loop join
Read data from outer row source (upper one)
Probe for a match in inner row source for each outer row
------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 37 | 4 |
| 1 | NESTED LOOPS | | 1 | 37 | 4 |
|* 2 | TABLE ACCESS FULL | EMP | 1 | 17 | 3 |
|* 3 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 20 | 1 |
|* 4 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | |
------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("E"."DEPTNO" IS NOT NULL AND "E"."ENAME" LIKE 'K%')
3 - filter("D"."DNAME"='SALES')
4 - access("E"."DEPTNO"="D"."DEPTNO")
Tanel Põder
Hash Join
Only for equijoins/non-equijoins (outer joins in 10g)
Builds an array with hashed key values from smaller row source
Scans the bigger row source, builds and compares hashed key values
on the fly, returns matching ones
SQL> select d.dname, d.loc, e.empno, e.ename
2 from emp e, dept d
3 where e.deptno = d.deptno
4 and d.dname = 'SALES'
5 and e.ename between 'A%' and 'M%';
----------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 37 | 9 (12)|
|* 1 | HASH JOIN | | 1 | 37 | 9 (12)|
|* 2 | TABLE ACCESS FULL| DEPT | 1 | 20 | 2 (0)|
|* 3 | TABLE ACCESS FULL| EMP | 4 | 68 | 6 (0)|
----------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("E"."DEPTNO"="D"."DEPTNO")
2 - filter("D"."DNAME"='SALES')
3 - filter("E"."DEPTNO" IS NOT NULL AND "E"."ENAME"<='M%'
AND"E"."ENAME">='A%')
Tanel Põder
Sort-Merge Join
Requires both rowsources to be sorted
Either by a sort operation
Or sorted by access path (index range and full scan)
Cannot return any rows before both rowsources are sorted (non-
cascading)
NL and Hash join should be normally preferred
SQL> select /*+ USE_MERGE(d,e) */ d.dname, d.loc, e.empno, e.ename
2 from emp e, dept d
3 where e.deptno = d.deptno
4 and d.dname = 'SALES'
5 and e.ename between 'A%' and 'X%'
6 order by e.deptno;
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1245 | 46065 | 64 (10)|
| 1 | MERGE JOIN | | 1245 | 46065 | 64 (10)|
|* 2 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 20 | 2 (0)|
| 3 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)|
|* 4 | SORT JOIN | | 3735 | 63495 | 62 (10)|
|* 5 | TABLE ACCESS FULL | EMP | 3735 | 63495 | 61 (9)|
-----------------------------------------------------------------------------
Tanel Põder
View merging
Optimizer merges subqueries, inline and stored views and runs
queries directly on base tables
Not always possible though due semantic reasons
SQL> create or replace view empview
2 as
Can be controlled using:
3 select e.empno, e.ename, d.dname
Parameter: _complex_view_merging
4 from emp e, dept d
_simple_view_merging
5 where e.deptno = d.deptno;
Hints: MERGE, NO_MERGE
SQL> select * from empview
2 where ename = 'KING';
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7 | 210 | 5 (20)|
|* 1 | HASH JOIN | | 7 | 210 | 5 (20)|
| 2 | TABLE ACCESS FULL | DEPT | 4 | 52 | 2 (0)|
|* 3 | TABLE ACCESS BY INDEX ROWID| EMP | 7 | 119 | 2 (0)|
|* 4 | INDEX RANGE SCAN | EMP_ENAME | 8 | | 1 (0)|
------------------------------------------------------------------------------
Tanel Põder
Subquery unnesting
Subqueries can be unnested, converted to anti- and semijoins
SQL> select * from employees e
2 where exists ( Can be controlled using:
3 select ename from bonus b Parameter: _unnest_subqueries
4 where e.ename = b.ename Hints: UNNEST, NO_UNNEST
5 );
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 37 | 5
| 1 | NESTED LOOPS | | 1 | 37 | 5
| 2 | NESTED LOOPS | | 1 | 24 | 4
| 3 | SORT UNIQUE | | 1 | 7 | 2
| 4 | TABLE ACCESS FULL | BONUS | 1 | 7 | 2
|* 5 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 17 | 1
|* 6 | INDEX RANGE SCAN | EMP_ENAME | 37 | | 1
| 7 | TABLE ACCESS BY INDEX ROWID | DEPT | 1 | 13 | 1
|* 8 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - filter("E"."DEPTNO" IS NOT NULL)
6 - access("E"."ENAME"="B"."ENAME")
8 - access("E"."DEPTNO"="D"."DEPTNO")
Tanel Põder
SQL execution plan recap (again)
Execution plan lines are just Oracle kernel functions!
In other words, each row source is a function
Tanel Põder
http://www.tanelpoder.com