Understand Execution Plan
Information
Execution Plan Columns
Column
Detailed Explanation
Id
A numeric identifier assigned to each step in the execution plan. The steps form a hierarchical tree, where higher-level steps (lower IDs) depend on lower-level operations. Oracle begins execution from the innermost child node (highest ID) and proceeds upwards. Indentation or tree structure (in some tools) reflects parent-child relationships between operations.
Operation
Describes the type of action Oracle performs at that step. Examples include:
• TABLE ACCESS FULL
– Full table scan
• INDEX RANGE SCAN
– Accessing index via range
• HASH JOIN
– Performing a join using hashing technique
• SORT AGGREGATE
– Aggregate function with sorting
This is the most crucial column to understand how Oracle is accessing data and combining it.
Name
Indicates the name of the object (table, index, or view) used in that step. This helps confirm whether Oracle is using the correct index, or if it's accessing the expected table (especially in views or synonyms). A blank value usually indicates that the operation does not directly access a named object (e.g., a scalar function or derived row).
Rows
Estimated number of rows that the step will output to its parent. This estimate is based on optimizer statistics (e.g., table size, data distribution, histograms). Accurate row estimates are crucial because they determine join methods, access paths, and overall cost. If the estimate is inaccurate, the optimizer may choose a suboptimal plan.
Bytes
Estimated size (in bytes) of the data output by this step. Calculated as: Estimated Rows × Average Row Size
.
This helps the optimizer understand memory and I/O implications, especially for operations like sorting, joins, or aggregation. High byte estimates may trigger use of TEMP tablespace or reduce parallelism.
Cost (%CPU)
A relative measure of resource usage, used by Oracle to compare alternative execution plans. The value is derived from a combination of factors like I/O cost, CPU usage, and memory. The number in parentheses is the percentage of that cost attributed to CPU. This is not an actual runtime metric but a modeling approximation. Lower cost generally implies better efficiency relative to other paths, not absolute performance.
Time
Estimated execution time for the step in HH:MM:SS
format. This is computed by Oracle using I/O and CPU cost formulas, assuming ideal conditions. This is not the actual time the step took in real execution—it is merely a projection. For real-time metrics, use DBMS_XPLAN.DISPLAY_CURSOR
with actual execution statistics.
Predicate Info (e.g., *
asterisk)
A *
next to the ID or operation indicates that a filter or access predicate is applied at that step. These predicates can be seen in detail when using DBMS_XPLAN.DISPLAY
with the +PREDICATE
or ALL
options. Predicates are key for understanding how efficiently Oracle is filtering data—either during access or after data is fetched.
Example 1
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 63 | 32 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | USER_ENTITY | 1 | 63 | 2 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | CONSTRAINT_FB | 1 | | 1 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED | USER_ATTRIBUTE | 1 | 102 | 30 (0)| 00:00:01 |
|* 4 | INDEX SKIP SCAN | IDX_USER_ATTRIBUTE_NAME | 2 | | 28 (0)| 00:00:01 |
Oracle uses cost-based optimization (CBO) to estimate query execution costs and choose the most efficient plan.
Explanation
Step 0 (Top Level) Oracle is executing a SELECT statement. The total estimated cost of the query is 32. That means Oracle expects this query to be relatively cheap, but not negligible.
Step 1 and 2
Oracle is accessing the USER_ENTITY
table using an index called CONSTRAINT_FB
. The scan type is INDEX UNIQUE SCAN, which is very efficient. It means Oracle expects only one row to match. This is a good sign.
Step 3 and 4
Oracle then accesses the USER_ATTRIBUTE
table. It is using Batched Rowid Access, which is more efficient than individual row-by-row lookups. However, the index being used is IDX_USER_ATTRIBUTE_NAME
with an Index Skip Scan.
An index skip scan is used when the leading column of a composite index is not part of the filter condition. Oracle skips through index blocks, which is more expensive than a range scan. This is where most of the cost lies — 28 for the index operation and 30 for the table access.
Observations
Overall Cost is
32
This is the estimated total cost for Oracle to execute this query.
Lower is better, but it depends on the dataset size and indexing.
INDEX UNIQUE SCAN (
CONSTRAINT_FB
) has Cost =1
Since it's a primary key lookup, it's extremely efficient.
It retrieves
userentity0_.id
using the primary key.
INDEX SKIP SCAN (
IDX_USER_ATTRIBUTE_NAME
) has Cost =28
Oracle is skipping over indexed values instead of performing a full scan.
Problem? If
user_attribute.value
has high cardinality (many distinct values), an INDEX RANGE SCAN would be better.
TABLE ACCESS BY INDEX ROWID (
USER_ATTRIBUTE
) has Cost =30
Fetching rows from
USER_ATTRIBUTE
based on theINDEX SKIP SCAN
result.The cost is high because many rows may match the filter on
value
.
How to Improve This Query?
1. Improve Indexing Strategy
Use a Composite Index (
value
,user_id
)CREATE INDEX idx_user_attribute_value_userid ON user_attribute(value, user_id);
This helps Oracle directly locate
user_id
, avoiding the skip scan.Expected result: The optimizer should switch to INDEX RANGE SCAN instead.
2. Force an Index Hint
If Oracle keeps using INDEX SKIP SCAN, you can force it to use a better index:
SELECT /*+ INDEX(userattrib1_ idx_user_attribute_value_userid) */
userentity0_.username
FROM user_entity userentity0_
INNER JOIN user_attribute userattrib1_
ON userentity0_.id = userattrib1_.user_id
WHERE userattrib1_.value = '1311310003';
3. Check Index Statistics
Run:
SELECT table_name, num_rows FROM user_tables WHERE table_name IN ('USER_ENTITY', 'USER_ATTRIBUTE');
SELECT index_name, num_rows, distinct_keys FROM user_indexes WHERE table_name = 'USER_ATTRIBUTE';
If
num_rows
is large butdistinct_keys
forIDX_USER_ATTRIBUTE_NAME
is low, the INDEX SKIP SCAN is inefficient.In this case, a new composite index (
value
,user_id
) will help.
Last updated
Was this helpful?