Execution plans are fantastic – they make it easier to visualize how a query is running and find tuning opportunities. SQL Server DBAs starting with Oracle may be a bit frustrated by the lack of a clear way to get to execution plans. This covers three ways to get to execution plans in Oracle – estimated plans, actual plans from an active session, and actual plans from the plan cache.
Getting Estimated Oracle Execution Plans
One way, likely more familiar to MySQL and PostgreSQL DBAs, is to use the EXPLAIN PLAN FOR
command to get an execution plan out of Oracle. To do this, a DBA would simply add the command EXPLAIN PLAN FOR
to the beginning of a SQL statement:
1 2 3 4 5 6 7 8 9 |
EXPLAIN PLAN FOR SELECT o.order_id, v.product_name FROM oe.orders o, ( SELECT order_id, product_name FROM oe.order_items o, oe.product_information p WHERE p.product_id = o.product_id AND list_price < 50 AND min_price < 40 ) v WHERE o.order_id = v.order_id; |
Oracle will immediately finish and return Explained.
SQL Server DBAs will likely exclaim “WTF, Oracle?” and stomp off in frustration in search of a GUI that will let them connect to Oracle and show them a graphical execution plan.
Oracle gives DBAs the ability to save off plans into tables, and that’s exactly what the EXPLAIN PLAN FOR
syntax does – it saves the plan into a table named plan_table
. You can view the execution plan using the following command:
1 2 |
SELECT plan_table_output FROM table(dbms_xplan.display()); |
This produces lovely text output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Plan hash value: 1906736282 --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 40 | 6 (0)| 00:00:01 | | 1 | NESTED LOOPS | | 1 | 40 | 6 (0)| 00:00:01 | | 2 | MERGE JOIN CARTESIAN| | 4 | 128 | 6 (0)| 00:00:01 | |* 3 | TABLE ACCESS FULL | PRODUCT_INFORMATION | 1 | 28 | 5 (0)| 00:00:01 | | 4 | BUFFER SORT | | 105 | 420 | 1 (0)| 00:00:01 | | 5 | INDEX FULL SCAN | ORDER_PK | 105 | 420 | 1 (0)| 00:00:01 | |* 6 | INDEX UNIQUE SCAN | ORDER_ITEMS_UK | 1 | 8 | 0 (0)| 00:00:01 | --------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 3 - filter("MIN_PRICE"<40 AND "LIST_PRICE"<50) 6 - access("O"."ORDER_ID"="ORDER_ID" AND "P"."PRODUCT_ID"="O"."PRODUCT_ID") 19 rows selected. |
There are three parts here – the plan hash value, the row source tree, and predicate information. Plans can be read similarly to SQL Server execution plans; just start at the deepest level and work your way out. In this case, start at Id
6 and work your way up to 3, then 2, then 1, then 0. An asterisk next to the Id
means that there’s more detailed information about that step in the Predicate Information
section.
You can get more detailed output from Oracle by supplying parameters to the DBMS_XPLAN.DISPLAY()
function:
1 2 |
select plan_table_output from table(dbms_xplan.display('plan_table', null, 'all')); |
More documentation on DBMS_XPLAN
can be found in the Oracle documentation.
Getting Actual Execution Plans From Oracle
Run the query first:
1 2 3 4 5 6 7 8 |
SELECT o.order_id, v.product_name FROM oe.orders o, ( SELECT order_id, product_name FROM oe.order_items o, oe.product_information p WHERE p.product_id = o.product_id AND list_price < 50 AND min_price < 40 ) v WHERE o.order_id = v.order_id; |
And then follow up by executing:
1 |
select * from table(dbms_xplan.display_cursor()); |
You should see the following execution plan:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
SQL_ID gpbdw897v4mbg, child number 0 ------------------------------------- SELECT o.order_id, v.product_name FROM oe.orders o, ( SELECT order_id, product_name FROM oe.order_items o, oe.product_information p WHERE p.product_id = o.product_id AND list_price < 50 AND min_price < 40 ) v WHERE o.order_id = v.order_id Plan hash value: 1906736282 --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 6 (100)| | | 1 | NESTED LOOPS | | 1 | 40 | 6 (0)| 00:00:01 | | 2 | MERGE JOIN CARTESIAN| | 4 | 128 | 6 (0)| 00:00:01 | |* 3 | TABLE ACCESS FULL | PRODUCT_INFORMATION | 1 | 28 | 5 (0)| 00:00:01 | | 4 | BUFFER SORT | | 105 | 420 | 1 (0)| 00:00:01 | | 5 | INDEX FULL SCAN | ORDER_PK | 105 | 420 | 1 (0)| 00:00:01 | |* 6 | INDEX UNIQUE SCAN | ORDER_ITEMS_UK | 1 | 8 | 0 (0)| | --------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 3 - filter(("MIN_PRICE"<40 AND "LIST_PRICE"<50)) 6 - access("O"."ORDER_ID"="ORDER_ID" AND "P"."PRODUCT_ID"="O"."PRODUCT_ID") 28 rows selected. |
Hey, that looks exactly the same! The default parameters for dbms_xplan.display_cursor
won’t show us all the juicy details that we want. Adding in FORMAT => '+ALLSTATS'
will do exactly what we want. Before you go running that execution plan query again, you have two choices: you can either execute the full select query again, or you can copy down the SQL_ID
and use it in the dbms_xplan.display_cursor
function like this:
1 2 3 |
select * from table(dbms_xplan.display_cursor(sql_id => 'gpbdw897v4mbg', format => '+ALLSTATS')); |
The output gets a bit more detailed and ends up looking like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
SQL_ID 5p20zwvbgb93j, child number 0 ------------------------------------- SELECT o.order_id, v.product_name FROM oe.orders o, ( SELECT order_id, product_name FROM oe.order_items o, oe.product_information p WHERE p.product_id = o.product_id AND list_price < 50 AND min_price < 40 ) v WHERE o.order_id = v.order_id Plan hash value: 1906736282 ---------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | O/1/M | ---------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 269 |00:00:00.08 | 1337 | | | | | 1 | NESTED LOOPS | | 1 | 1 | 269 |00:00:00.08 | 1337 | | | | | 2 | MERGE JOIN CARTESIAN| | 1 | 4 | 9135 |00:00:00.03 | 34 | | | | |* 3 | TABLE ACCESS FULL | PRODUCT_INFORMATION | 1 | 1 | 87 |00:00:00.01 | 33 | | | | | 4 | BUFFER SORT | | 87 | 105 | 9135 |00:00:00.01 | 1 | 4096 | 4096 | 1/0/0| | 5 | INDEX FULL SCAN | ORDER_PK | 1 | 105 | 105 |00:00:00.01 | 1 | | | | |* 6 | INDEX UNIQUE SCAN | ORDER_ITEMS_UK | 9135 | 1 | 269 |00:00:00.02 | 1303 | | | | ---------------------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 3 - filter(("MIN_PRICE"<40 AND "LIST_PRICE"<50)) 6 - access("O"."ORDER_ID"="ORDER_ID" AND "P"."PRODUCT_ID"="O"."PRODUCT_ID") 28 rows selected. |
Now we can see the estimated (E-Rows) and actual (A-Rows) returned from each operation as well as timing and memory information. Parallel queries will return even more information (take a look at the example DBMS_XPLAN
commands and output to see what that would look like.
Viewing The Oracle Execution Plan Cache
You might hear Oracle people talk about this as the library cache or the cursor cache. In the SQL Server world, we’d call it the execution plan cache. Whatever you want to call it, it’s contained in a system view: V$SQL_PLAN
.
We could find our query in V$SQL_PLAN
and get the execution plan doing something like this:
1 2 3 4 5 |
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 o.order_id, v.product_name%'; |
This query will pull back basic information about the different statements that have been executed that match our search condition. That output looks different because I used thebasic
parameter for plan display instead of the default of typical
, but here’s an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
EXPLAINED SQL STATEMENT: ------------------------ SELECT o.order_id, v.product_name FROM oe.orders o, ( SELECT order_id, product_name FROM oe.order_items o, oe.product_information p WHERE p.product_id = o.product_id AND list_price < 50 AND min_price < 40 ) v WHERE o.order_id = v.order_id Plan hash value: 1906736282 ----------------------------------------------------- | Id | Operation | Name | ----------------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | NESTED LOOPS | | | 2 | MERGE JOIN CARTESIAN| | | 3 | TABLE ACCESS FULL | PRODUCT_INFORMATION | | 4 | BUFFER SORT | | | 5 | INDEX FULL SCAN | ORDER_PK | | 6 | INDEX UNIQUE SCAN | ORDER_ITEMS_UK | ----------------------------------------------------- |
If you want to get the full plan from any of these statements, you can either include the sql_id
in the query to find all plans for a statement, or you can use the plan hash value to get retrieve it from the v$sql_plan
view and use it in the dbms_xplan.display_cursor
function.
In the query to retrieve matching plans, our query uses Oracle’s v$sql_plan
system view to find matching cursors (everything’s a cursor, yay) in memory. Once we have a list of matching cursors in memory, dbms_xplan.display_cursor
is used to pull back information about the actual plan from the cursor cache.
Wrapping Up
There you have it – three ways to view execution plans in Oracle:
- Using
EXPLAIN PLAN FOR
to get an estimated execution plan. - Using
dbms_xplan.display_cursor()
after running a query to get the actual execution plan. - Querying
v$sql_plan
to find plans in the plan cache.
SQL Server experts and novices alike can get the kind of query details from Oracle that they’re used to seeing from SQL Server.
3 Comments. Leave new
There is a great extension to dbms_xplan that was developed by Adrian Billington on his site here: http://www.oracle-developer.net/utilities.php
The first one (Xplan utility) takes the output and adds an extra column – execution order – to easily see the order that the steps in the execution plan are executed.
For long / complex queries, it’s definitely much simpler to decode the output than the standard fare offered out of the box.
That is awesome, thanks for sharing that tool. I’ll try to get it worked into the article in the next few days.
If you have an account with Oracle Support ($), then take a look at SQLTXPLAIN:
http://carlos-sierra.net/2012/04/03/what-is-sqltxplain/
Free (yet good) System/SQL “what’s happening?” tool:
http://carlos-sierra.net/2015/02/16/learn-how-free-new-tool-sqld360-can-tell-you-so-much-about-your-favorite-sql/