4. What is an Execution plan?
• Execution plans show the detailed steps necessary to
execute a SQL statement
• These steps are expressed as a set of database
operators that consumes and produces rows
• The order of the operators and their implementation is
decided by the optimizer using a combination of query
transformations and physical optimization techniques
• The display is commonly shown in a tabular format,
but a plan is in fact tree-shaped
5. Group By
HASH JOIN
TABLE ACCESS
SALES
TABLE ACCESS
PRODUCTS
What is an Execution plan?
Query
SELECT prod_category, avg(amount_sold)
FROM sales s, products p
WHERE p.prod_id = s.prod_id
GROUP BY prod_category;
Tabular representation of plan
-----------------------------------------
Id Operation Name
-----------------------------------------
0 SELECT STATEMENT
1 HASH GROUP BY
2 HASH JOIN
3 TABLE ACCESS FULL PRODUCTS
4 PARTITION RANGE ALL
5 TABLE ACCESS FULL SALES
-----------------------------------------
Tree-shaped representation of plan
6. How to get an Execution Plan
Two methods for looking at the execution plan
1.EXPLAIN PLAN command
• Displays an execution plan for a SQL statement without actually
executing the statement
2.V$SQL_PLAN
• A dictionary view introduced in Oracle 9i that shows the execution
plan for a SQL statement that has been compiled into a cursor in
the cursor cache
Use DBMS_XPLAN package to display plans
Under certain conditions the plan shown with EXPLAIN PLAN
can be different from the plan shown using V$SQL_PLAN
7. How to get an Execution Plan
Example 1 EXPLAIN PLAN command & dbms_xplan.display function
SQL> EXPLAIN PLAN FOR SELECT prod_category, avg(amount_sold)
FROM sales s, products p
WHERE p.prod_id = s.prod_id
GROUP BY prod_category;
Explained
SQL> SELECT plan_table_output
FROM table(dbms_xplan.display('plan_table',null,'basic'));
------------------------------------------
Id Operation Name
------------------------------------------
0 SELECT STATEMENT
1 HASH GROUP BY
2 HASH JOIN
3 TABLE ACCESS FULL PRODUCTS
4 PARTITION RANGE ALL
5 TABLE ACCESS FULL SALES
-------------------------------------------
8. Explain Plan “lies”
• Explain plan should hardly ever be used…
• You have to be careful when using autotrace and
related tools
• Never use “explain=u/p” with tkprof
• Avoid dbms_xplan.display, use display_cursor
9. Explain plan lies…
ops$tkyte%ORA11GR2> create table t
2 as
3 select 99 id, to_char(object_id) str_id, a.*
4 from all_objects a
5 where rownum <= 20000;
Table created.
ops$tkyte%ORA11GR2> update t
2 set id = 1
3 where rownum = 1;
1 row updated.
ops$tkyte%ORA11GR2> create index t_idx on t(id);
Index created.
ops$tkyte%ORA11GR2> create index t_idx2 on t(str_id);
Index created.
12. Explain plan lies…
Need a volunteer
select count(*) from t where id = :n;
What cardinality would you estimate
and why?
13. Explain plan lies…
ops$tkyte%ORA11GR2> variable n number
ops$tkyte%ORA11GR2> exec :n := 1;
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR2> set autotrace on explain
ops$tkyte%ORA11GR2> select count(subobject_name) from t where id = :n;
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 20 | 86 (0)| 00:00:02 |
| 1 | SORT AGGREGATE | | 1 | 20 | | |
|* 2 | TABLE ACCESS FULL| T | 10000 | 195K| 86 (0)| 00:00:02 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("ID"=TO_NUMBER(:N)) <<<<===== to_number?
14. Explain plan lies…
ops$tkyte%ORA11GR2> select * from
table(dbms_xplan.display_cursor('98mx7jbn7jpm8', '', '+peeked_binds'));
select count(subobject_name) from t where id = :n
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | SORT AGGREGATE | | 1 | 20 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| T | 1 | 20 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | T_IDX | 1 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Peeked Binds (identified by position):
--------------------------------------
1 - :N (NUMBER): 1
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("ID"=:N)
15. Explain plan lies…
ops$tkyte%ORA11GR2> set autotrace traceonly explain
ops$tkyte%ORA11GR2> select object_id from t where str_id = :n;
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 19 | 2 (0)| 00:0
| 1 | TABLE ACCESS BY INDEX ROWID| T | 1 | 19 | 2 (0)| 00:0
|* 2 | INDEX RANGE SCAN | T_IDX2 | 1 | | 1 (0)| 00:0
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("STR_ID"=:N) <<== interesting…
16. Explain plan lies…
ops$tkyte%ORA11GR2> select object_id from t where str_id = :n;
OBJECT_ID
----------
99
ops$tkyte%ORA11GR2> select * from table(dbms_xplan.display_cursor);
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 86 (100)| |
|* 1 | TABLE ACCESS FULL| T | 1 | 19 | 86 (0)| 00:00:02 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TO_NUMBER("STR_ID")=:N) <<= string has to convert..
17. Explain plan lies…
1 - filter(TO_NUMBER("STR_ID")=:N) <<= string has to convert..
STR_ID
------
0
00
000
0.00
+0
-0
1,000
1.000
18. How to get an Execution Plan
Example 2 Generate & display execution plan for the last SQL stmts
executed in a session
SQL>SELECT prod_category, avg(amount_sold)
FROM sales s, products p
WHERE p.prod_id = s.prod_id
GROUP BY prod_category;
no rows selected
SQL> SELECT plan_table_output
FROM table(dbms_xplan.display_cursor(null,null,'basic'));
------------------------------------------
Id Operation Name
------------------------------------------
0 SELECT STATEMENT
1 HASH GROUP BY
2 HASH JOIN
3 TABLE ACCESS FULL PRODUCTS
4 PARTITION RANGE ALL
5 TABLE ACCESS FULL SALES
-------------------------------------------
19. How to get an Execution Plan
Example 3 Displaying execution plan for any other statement from
V$SQL_PLAN
1.Directly:
SQL> SELECT plan_table_output FROM
table(dbms_xplan.display_cursor('fnrtqw9c233tt',null,'basic'));
2.Indirectly:
SQL> SELECT plan_table_output
FROM v$sql s,
TABLE(dbms_xplan.display_cursor(s.sql_id,s.child_number, 'basic')) t
WHERE s.sql_text like 'select PROD_CATEGORY%';
24. What’s a Good Plan for the Optimizer?
The Optimizer has two different goals
• Serial execution: It’s all about cost
• The cheaper, the better
• Parallel execution: it’s all about performance
• The faster, the better
Two fundamental questions:
• What is cost?
• What is performance?
25. What is Cost?
• A magically number the optimizer makes up?
• Resources required to execute a SQL statement?
• Result of complex calculations?
• Estimate of how long it will take to execute a statement?
Actual Definition
• Cost represents units of work or resources used
• Optimizer uses CPU & memory usage plus IO as units of work
• Cost is an estimate of the amount of CPU and memory plus the number of
disk I/Os, used in performing an operation
Cost is an internal Oracle measurement
26. Clustering Factor and Cost Example
ops$tkyte%ORA11GR2> create table organized
2 as
3 select x.*
4 from (select * from stage order by object_name) x
5 /
Table created.
ops$tkyte%ORA11GR2> create table disorganized
2 as
3 select x.*
4 from (select * from stage order by dbms_random.random) x
5 /
Table created.
27. Clustering Factor and Cost Example
ops$tkyte%ORA11GR2> create index organized_idx on
organized(object_name);
Index created.
ops$tkyte%ORA11GR2> create index disorganized_idx on
disorganized(object_name);
Index created.
40. SQL Execution Plan
When looking at a plan can you determine if the following is correct?
• Cardinality
• Are the correct number of rows coming out of each object?
• Access paths
• Is the data being accessed in the best way? Scan? Index lookup?
• Join order
• Are tables being joined in the correct order to eliminate as much data as early as possible?
• Join type
• Are the right join types being used?
• Partitioning pruning
• Did I get partition pruning? Is it eliminating enough data?
• Parallelism
41. Cardinality
What is it?
• Estimate of number rows that will be returned
• Cardinality for a single value predicate = num_rows total / num_distinct total
• E.g. 100 rows total, 10 distinct values => cardinality=10 rows
• OR if histogram present num_rows * Density
Why should you care?
• Influences everything! Access method, Join type, Join Order etc
What causes Cardinality to be wrong?
• Stale or no statistics
• Data Skews
• Multiple single column predicates on a table
• A function wrapped where clause predicate
• Complicated expressions that contain columns from different tables
43. Wrong Plan => Wrong Cardinality
ops$tkyte%ORA11GR2> create table t
2 as select decode( mod(rownum,2), 0, 'N', 'Y' ) flag1,
3 decode( mod(rownum,2), 0, 'Y', 'N' ) flag2, a.*
4 from all_objects a
5 /
Table created.
ops$tkyte%ORA11GR2> create index t_idx on t(flag1,flag2);
Index created.
ops$tkyte%ORA11GR2> begin
2 dbms_stats.gather_table_stats
3 ( user, 'T',
4 method_opt=>'for all indexed columns size 254' );
5 end;
6 /
PL/SQL procedure successfully completed.
44. Wrong Plan => Wrong Cardinality
ops$tkyte%ORA11GR2> select 'select * from t', num_rows
2 from user_tables where table_name = 'T'
3 union all
4 select 'select * from t where flag1 = "N"', num_rows/2
5 from user_tables where table_name = 'T'
6 union all
7 select 'select * from t where flag2 = "N"', num_rows/2
8 from user_tables where table_name = 'T'
9 union all
10 select 'select * from t where flag1 = "N" and flag2 = "N"', num_rows/2/2
11 from user_tables where table_name = 'T';
'SELECT*FROMT' NUM_ROWS
------------------------------------------------- ----------
select * from t 72726
select * from t where flag1 = "N" 36363
select * from t where flag2 = "N" 36363
select * from t where flag1 = "N" and flag2 = "N" 18181.5
45. Wrong Plan => Wrong Cardinality
ops$tkyte%ORA11GR2> set autotrace traceonly explain
ops$tkyte%ORA11GR2> select * from t where flag1='N';
Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 36499 | 3635K| 301 (1)| 00:00:04 |
|* 1 | TABLE ACCESS FULL| T | 36499 | 3635K| 301 (1)| 00:00:04 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("FLAG1"='N')
46. Wrong Plan => Wrong Cardinality
ops$tkyte%ORA11GR2> select * from t where flag2='N';
Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 36227 | 3608K| 301 (1)| 00:00:04 |
|* 1 | TABLE ACCESS FULL| T | 36227 | 3608K| 301 (1)| 00:00:04 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("FLAG2"='N')
47. Wrong Plan => Wrong Cardinality
ops$tkyte%ORA11GR2> select * from t where flag1='N' and flag2='N';
Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 18181 | 1810K| 301 (1)| 00:00:04 |
|* 1 | TABLE ACCESS FULL| T | 18181 | 1810K| 301 (1)| 00:00:04 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("FLAG2"='N' AND "FLAG1"='N')
48. Wrong Plan => Wrong Cardinality
ops$tkyte%ORA11GR2> select /*+ gather_plan_statistics */ *
2 from t where flag1='N' and flag2='N';
no rows selected
49. Wrong Plan => Wrong Cardinality
ops$tkyte%ORA11GR2> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 0 |00:00:00.02 | 1080 |
|* 1 | TABLE ACCESS FULL| T | 1 | 18181 | 0 |00:00:00.02 | 1080 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(("FLAG2"='N' AND "FLAG1"='N'))
19 rows selected.
50. Wrong Plan => Wrong Cardinality
ops$tkyte%ORA11GR2> select /*+ dynamic_sampling(t 3) */ * from t where flag1='N' and flag2='N';
Execution Plan
----------------------------------------------------------
Plan hash value: 470836197
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 612 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 6 | 612 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | T_IDX | 6 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("FLAG1"='N' AND "FLAG2"='N')
Note
-----
- dynamic sampling used for this statement (level=2)
51. Wrong Plan => Wrong Cardinality
SELECT /* OPT_DYN_SAMP */ /*+ ALL_ROWS IGNORE_WHERE_CLAUSE
NO_PARALLEL(SAMPLESUB) opt_param('parallel_execution_enabled', 'false')
NO_PARALLEL_INDEX(SAMPLESUB) NO_SQL_TUNE */
NVL(SUM(C1),:"SYS_B_00"), NVL(SUM(C2),:"SYS_B_01"), NVL(SUM(C3),:"SYS_B_02")
FROM
(SELECT /*+ IGNORE_WHERE_CLAUSE NO_PARALLEL("T") FULL("T")
NO_PARALLEL_INDEX("T") */
:"SYS_B_03" AS C1,
CASE WHEN "T"."FLAG1"= :"SYS_B_04" AND "T"."FLAG2"=:"SYS_B_05"
THEN :"SYS_B_06"
ELSE :"SYS_B_07"
END AS C2,
CASE WHEN "T"."FLAG2"=:"SYS_B_08" AND "T"."FLAG1"=:"SYS_B_09“
THEN :"SYS_B_10"
ELSE :"SYS_B_11"
END AS C3
FROM "OPS$TKYTE"."T"
SAMPLE BLOCK (:"SYS_B_12" , :"SYS_B_13") SEED (:"SYS_B_14") "T") SAMPLESUB
52. Check Cardinality using
SELECT /*+ gather_plan_statistics */
p.prod_name as product, sum(s.quantity_sold) as units,
FROM sales s, products p
WHERE s.prod_id =p.prod_id
GROUP BY p.prod_name;
SELECT * FROM table (
DBMS_XPLAN.DISPLAY_CURSOR(FORMAT=>'ALLSTATS LAST'));
compare the estimated number of rows returned for each operation in the plan to actual rows returned
53. Check Cardinality using SQL Monitor
SQL Monitor allows you to compare the
estimated number of rows returned for each
operation in the plan to actual rows returned
54. Suggestions for fixing Cardinality issues
Cause Solution
Stale or no statistics DBMS_STATS
Data Skew Create a histogram*
Multiple single column predicates on a
table
Create a column group using
DBMS_STATS.CREATE_EXTENDED_STATS
Multiple columns used in a join Create a column group using
DBMS_STATS.CREATE_EXTENDED_STATS
Function wrapped column Create statistics on the function wrapped column using
DBMS_STATS.CREATE_EXTENDED_STATS
Complicated expression containing
columns from multiple tables
Use dynamic sampling level 4 or higher