Introduction
When facing performance issues in an Oracle database, one of the most critical tasks is analyzing the Execution Plan. While tools like AWR and ASH can help identify high-load SQL statements, understanding the execution plan is essential to determine the root cause of why a query is slow.
In this article, we systematically explain everything from the basics of Oracle execution plans to practical analysis and tuning techniques that can be applied in real-world scenarios.
What Is an Execution Plan?
When Oracle receives a SQL statement, it does not execute it as-is. Instead, it internally converts the SQL into what it determines to be the most efficient sequence of operations. This sequence is called the execution plan.
An execution plan determines key aspects such as:
- Table access methods (full scan vs. index access)
- Join order of tables
- Join methods (nested loops, hash join, etc.)
Even for the same SQL, the execution plan can change depending on data volume and statistics, leading to significant performance differences. For example, a query that completes in milliseconds using an index may take several seconds if a full table scan is chosen.
When SQL performance issues occur, reviewing the execution plan—alongside AWR and ASH analysis—helps identify bottlenecks and enables more effective tuning.
For details on AWR and ASH analysis, refer to the following:
- Oracle AWR Report Guide: Key Analysis Points for Performance Tuning and Troubleshooting
- Oracle ASH Analysis Guide: Practical Techniques to Diagnose CPU, I/O, and Lock Issues
How to Obtain an Execution Plan
Let’s start by retrieving an execution plan. In this example, we create a test table for analysis.
SQL> CREATE TABLE test_table AS
SELECT
LEVEL AS id,
DBMS_RANDOM.STRING('A', 100) AS data
FROM dual
CONNECT BY LEVEL <= 1000000;
Using DBMS_XPLAN.DISPLAY_CURSOR (Actual Execution Plan)
By using the DBMS_XPLAN.DISPLAY_CURSOR function, you can view both estimated and actual execution statistics for executed SQL. When investigating performance issues, this is recommended over DBMS_XPLAN.DISPLAY.
You can also retrieve plans using a specific SQL_ID.
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('cp7rzu66ff0qt', NULL, 'ALLSTATS LAST'));
Parameters
- First argument (‘cp7rzu66ff0qt’)
Specifies the SQL_ID. If NULL, the most recently executed SQL is used. - Second argument (NULL)
Specifies the child cursor. NULL targets all child cursors. - Third argument (‘ALLSTATS LAST’)
Specifies the display format.
This option shows actual execution statistics, including:- Actual rows (A-Rows)
- I/O metrics
- Execution time
This allows comparison with optimizer estimates (E-Rows), which is crucial for SQL tuning.
Executing the Target SQL
SELECT /*+ gather_plan_statistics */ *
FROM test_table
WHERE data LIKE 'ABC%';
Retrieve the SQL_ID:
SELECT sql_id, sql_text
FROM v$sql
WHERE sql_text LIKE '%test_table%';
Then fetch the execution plan:
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('7bs0s1umbq307', NULL, 'ALLSTATS LAST'));
Key Metrics Explained
- SQL_ID: Identifier of the SQL statement
- Child Number: Identifies a specific child cursor
- Plan Hash Value: Unique identifier of the execution plan structure
- Id: Step number in the execution plan
- Operation: Operation performed
- Name: Object name
- Starts: Number of executions
- E-Rows: Estimated rows
- A-Rows: Actual rows
- A-Time: Actual execution time
- Buffers: Logical I/O (important for tuning)
- Reads: Physical I/O
The most important point is the gap between E-Rows (estimate) and A-Rows (actual), which often indicates the root cause of performance issues.
Using DBMS_XPLAN.DISPLAY (Estimated Plan)
DBMS_XPLAN.DISPLAY shows execution plans generated by EXPLAIN PLAN. These are estimates, not actual results.
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'ALL'));
To generate a plan without executing SQL:
EXPLAIN PLAN FOR
SELECT * FROM test_table WHERE data LIKE 'ABC%';
Then:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format => 'ALL'));
Key Metrics
- Rows: Estimated rows
- Bytes: Estimated data size
- Cost: Relative cost
- %CPU: CPU cost ratio (not actual CPU usage)
- Time: Estimated execution time
Note: This does not include actual execution statistics.
How to Analyze Execution Plans
Execution plans have a tree structure and should be read from bottom to top (right-indented operations first).
Table Access Methods
In execution plans, table access methods appear in the Operation column.
Example:
TABLE ACCESS FULL
Common Access Methods
- TABLE ACCESS FULL
Reads all rows sequentially
→ Slow for large tables (high I/O) - TABLE ACCESS BY INDEX ROWID
Uses index to fetch ROWIDs, then retrieves rows
→ Two-step access - INDEX RANGE SCAN
Scans a portion of an index
→ Efficient for range queries - INDEX UNIQUE SCAN
Fetches a single row via unique index
→ Very fast
Example: Adding an Index
CREATE INDEX idx_test_data ON test_table(data);
After adding the index, the plan changes from full scan to index access, significantly improving performance.
Join Order
The optimizer determines the join order.
Key principle:
Start with smaller datasets to reduce intermediate result size.
Example findings:
- Optimized plan: ~3.7 seconds
- Forced join order: ~11.1 seconds
Incorrect join order increases processing time significantly.
Important points:
- Optimizer decisions are often optimal
- Forcing with hints does not always improve performance
- Focus on Buffers (logical I/O) rather than just execution time
Join Methods
Join method selection has a major impact on performance.
Common Join Types
- NESTED LOOPS JOIN
Best for small datasets with indexes
Weak for large datasets - HASH JOIN
Best for large datasets without indexes
Requires sufficient memory - SORT MERGE JOIN
Used when sorting is required
Higher cost due to sort operations
Example Comparison
- HASH JOIN: ~3.53 seconds
- NESTED LOOPS: ~16.58 seconds
Nested loops caused excessive random I/O due to repeated index access.
Summary
Key takeaways:
- Execution plans reveal the true nature of SQL performance
- Always check actual execution stats (DISPLAY_CURSOR)
- Compare E-Rows vs. A-Rows
- Focus on Buffers (logical I/O)
- Join order and join method greatly impact performance
- Hints should be a last resort
By properly understanding execution plans, you can accurately identify SQL bottlenecks and achieve effective performance tuning.


コメント