Oracle Database
Performance Tuning
Performance tuning is the discipline of finding why a database is slow and fixing the root cause. Lesson 32 introduced the basics — execution plans, statistics, and index anti-patterns. This lesson goes deeper: using Oracle's diagnostic infrastructure to identify what is actually consuming resources, finding the specific SQL that is causing problems, and applying the SQL Tuning Advisor to fix it. The most important rule in performance tuning is to measure first — never guess.
AWR — Automatic Workload Repository
The Automatic Workload Repository (AWR) is Oracle's built-in performance data warehouse. Every hour by default, Oracle takes a snapshot of key performance statistics — wait events, SQL execution counts, CPU usage, I/O rates, memory usage. By comparing two snapshots you can see exactly what changed between any two points in time. AWR is the starting point for almost all serious performance investigations. It requires the Oracle Diagnostics Pack licence.
-- List recent AWR snapshots -- each row is one hourly snapshot
SELECT snap_id, begin_interval_time, end_interval_time
FROM dba_hist_snapshot
ORDER BY snap_id DESC
FETCH FIRST 10 ROWS ONLY;
-- Generate an AWR report between two snapshots as text
-- Replace 1 with your dbid, and 42/43 with your snap_id range
SELECT output
FROM TABLE(DBMS_WORKLOAD_REPOSITORY.AWR_REPORT_TEXT(
l_dbid => (SELECT dbid FROM v$database),
l_inst_num => 1,
l_bid => 42, -- beginning snapshot id
l_eid => 43 -- ending snapshot id
));
-- Check how often AWR snapshots are taken and how long they are retained
SELECT snap_interval, retention
FROM dba_hist_wr_control;SNAP_ID | BEGIN_INTERVAL_TIME | END_INTERVAL_TIME
--------|--------------------------|------------------------
44 | 07-MAR-26 09.00.00.000 | 07-MAR-26 10.00.00.000
43 | 07-MAR-26 08.00.00.000 | 07-MAR-26 09.00.00.000
42 | 07-MAR-26 07.00.00.000 | 07-MAR-26 08.00.00.000
(3 rows selected)
-- DBA_HIST_WR_CONTROL
SNAP_INTERVAL | RETENTION
--------------|----------
+00000 01:00 | +00008 00:00 (1 hour intervals, 8 day retention)
- AWR retains snapshots for 8 days by default — increase retention if you need to compare week-on-week patterns
- The AWR text report contains a Top 5 Wait Events section — if a single wait event dominates it tells you exactly where the bottleneck is (CPU, I/O, locking, network)
- AWR also ranks SQL statements by elapsed time, CPU time, buffer gets, and disk reads — the top SQL list is the fastest way to find the worst-performing queries
DBMS_WORKLOAD_REPOSITORYrequires the Diagnostics Pack licence — check your licence before using it in production
ASH — Active Session History
The Active Session History (ASH) samples every active session in the database once per second and records what each session is doing — which SQL it is running, which wait event it is experiencing, which object it is accessing. This data is held in memory in V$ACTIVE_SESSION_HISTORY and flushed to disk in DBA_HIST_ACTIVE_SESS_HISTORY. ASH is invaluable for diagnosing intermittent problems — spikes that lasted only a few minutes and are gone by the time you investigate.
-- Top wait events in the last 30 minutes from ASH (in-memory, recent data)
SELECT wait_class, event,
COUNT(*) AS sample_count,
ROUND(COUNT(*) * 100
/ SUM(COUNT(*)) OVER(), 1) AS pct_of_total
FROM v$active_session_history
WHERE sample_time >= SYSTIMESTAMP - INTERVAL '30' MINUTE
AND session_state = 'WAITING'
GROUP BY wait_class, event
ORDER BY sample_count DESC
FETCH FIRST 10 ROWS ONLY;
-- Top SQL statements by total database time in the last hour (from ASH)
SELECT sql_id,
COUNT(*) AS ash_samples,
COUNT(*) * 10 AS est_seconds -- each sample ~ 1 second of DB time
FROM v$active_session_history
WHERE sample_time >= SYSTIMESTAMP - INTERVAL '1' HOUR
AND sql_id IS NOT NULL
GROUP BY sql_id
ORDER BY ash_samples DESC
FETCH FIRST 10 ROWS ONLY;WAIT_CLASS | EVENT | SAMPLE_COUNT | PCT_OF_TOTAL
----------------|------------------------------|--------------|-------------
User I/O | db file sequential read | 312 | 62.4
Concurrency | enq: TX - row lock contention| 89 | 17.8
CPU | ON CPU | 61 | 12.2
(3 rows selected)
-- Top SQL by ASH samples
SQL_ID | ASH_SAMPLES | EST_SECONDS
--------------|-------------|------------
8fk2mz9dqr1p7 | 218 | 2180
3ty1nxq8vw0a2 | 94 | 940
db file sequential readis a single-block read — high counts mean Oracle is doing many index lookups; normal but can indicate too many index scans on a large result setenq: TX - row lock contentionmeans sessions are waiting for row locks held by other sessions — investigate blocking transactions- Each ASH sample represents approximately one second of database time — multiply sample count by the sample interval to estimate total seconds consumed
sql_idfrom ASH links directly toV$SQLandDBA_HIST_SQLSTATwhere you can retrieve the full SQL text and its execution statistics
Finding and Diagnosing Problem SQL
Once AWR or ASH identifies the sql_id of a slow statement, pull its full text, execution statistics, and execution plan from the data dictionary. The most useful views are V$SQL for currently cached statements and DBA_HIST_SQLSTAT for historical statistics across AWR snapshots.
-- Retrieve the SQL text for a given sql_id
SELECT sql_id, sql_fulltext, executions,
ROUND(elapsed_time / 1e6, 2) AS total_elapsed_sec,
ROUND(elapsed_time / NULLIF(executions, 0) / 1e6, 3) AS avg_elapsed_sec,
buffer_gets,
ROUND(buffer_gets / NULLIF(executions, 0)) AS avg_buffer_gets
FROM v$sql
WHERE sql_id = '8fk2mz9dqr1p7';
-- Display the current execution plan for a cached statement
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('8fk2mz9dqr1p7', NULL, 'ALLSTATS LAST'));
-- Find the top 10 SQL statements by average elapsed time (requires DBA access)
SELECT sql_id,
ROUND(AVG(elapsed_time_delta) / 1e6, 3) AS avg_elapsed_sec,
SUM(executions_delta) AS total_executions
FROM dba_hist_sqlstat
WHERE snap_id BETWEEN 42 AND 44
GROUP BY sql_id
ORDER BY avg_elapsed_sec DESC
FETCH FIRST 10 ROWS ONLY;SQL_ID | EXECUTIONS | TOTAL_ELAPSED_SEC | AVG_ELAPSED_SEC | AVG_BUFFER_GETS
--------------|------------|-------------------|-----------------|----------------
8fk2mz9dqr1p7 | 1840 | 2180.00 | 1.185 | 48200
-- DBMS_XPLAN.DISPLAY_CURSOR output
Plan hash value: 2910483721
| Id | Operation | Name | Rows | E-Rows | A-Rows |
|----|----------------------|------------|-------|--------|--------|
| 0 | SELECT STATEMENT | | | | 18400 |
|* 1 | TABLE ACCESS FULL | ORDERS | 18400 | 5 | 18400 |
Predicate Information:
1 - filter("STATUS"='pending')
elapsed_timeinV$SQLis stored in microseconds — divide by 1,000,000 to convert to secondsNULLIF(executions, 0)prevents a divide-by-zero error for statements that have been parsed but not yet executedALLSTATS LASTinDBMS_XPLAN.DISPLAY_CURSORshows both estimated rows (E-Rows) and actual rows (A-Rows) from the last execution — a large discrepancy between the two is a strong sign of stale statistics- In this example the plan shows TABLE ACCESS FULL with E-Rows=5 but A-Rows=18400 — the optimiser severely underestimated the row count, likely due to stale statistics on the status column
SQL Tuning Advisor
The SQL Tuning Advisor is an Oracle built-in tool that analyses a specific SQL statement and recommends improvements — better statistics, new indexes, SQL profile, or query restructuring. You run it through the DBMS_SQLTUNE package. It requires the Oracle Tuning Pack licence. When the advisor recommends a SQL Profile, accepting it stores corrected cardinality estimates that override the optimiser's guesses without changing the SQL text — useful when you cannot modify application code.
-- Run the SQL Tuning Advisor on a specific sql_id
DECLARE
v_task_name VARCHAR2(100);
BEGIN
v_task_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(
sql_id => '8fk2mz9dqr1p7',
scope => 'COMPREHENSIVE', -- full analysis including index recommendations
time_limit => 60, -- maximum seconds the advisor may run
task_name => 'tune_orders_query',
description => 'Tuning slow orders query'
);
DBMS_SQLTUNE.EXECUTE_TUNING_TASK(task_name => 'tune_orders_query');
END;
/
-- Read the advisor's recommendations
SELECT DBMS_SQLTUNE.REPORT_TUNING_TASK('tune_orders_query') AS report
FROM DUAL;
-- Accept a SQL Profile recommendation if the advisor suggests one
-- A SQL Profile stores corrected cardinality estimates -- no SQL change needed
DBMS_SQLTUNE.ACCEPT_SQL_PROFILE(
task_name => 'tune_orders_query',
replace => TRUE
);GENERAL INFORMATION
Task Name : tune_orders_query
Scope : COMPREHENSIVE
FINDINGS (2 findings, 2 recommendations)
FINDING 1: Statistics Finding
Table "DATAPLEXA_STORE"."ORDERS" has stale statistics.
Recommendation: gather statistics on this table.
Estimated benefit: 86%
FINDING 2: Index Finding
Creating an index on STATUS could reduce cost by 73%.
Recommendation: CREATE INDEX idx_orders_status ON orders(status);
RATIONALE: This index reduces rows examined from 18400 to 142.
- The advisor found the same two issues visible in the execution plan — stale statistics and a missing index on the status column
scope => 'COMPREHENSIVE'enables index recommendations and SQL profile generation —'LIMITED'scope only checks statistics and SQL structure- A SQL Profile is a better solution than a hint when you cannot touch the application SQL — it corrects the optimiser's estimates persistently without modifying the query text
- Always test advisor recommendations on a non-production system before applying them — an index that helps one query may slow down DML on a write-heavy table
Lesson Summary
| Concept | What It Means |
|---|---|
| AWR | Hourly performance snapshots — compare two snapshots to see what changed |
| AWR snapshot | Point-in-time capture of wait events, SQL stats, CPU, and I/O — stored in DBA_HIST_SNAPSHOT |
| ASH | 1-second samples of active sessions — V$ACTIVE_SESSION_HISTORY for recent, DBA_HIST for historical |
| Top 5 Wait Events | Key section of the AWR report — the dominant wait event identifies the bottleneck type |
| V$SQL | Currently cached SQL statements with execution statistics — elapsed time in microseconds |
| ALLSTATS LAST | DBMS_XPLAN option that shows estimated vs actual rows — large gap means stale statistics |
| SQL Tuning Advisor | DBMS_SQLTUNE — analyses a SQL statement and recommends statistics, indexes, or SQL profiles |
| SQL Profile | Stored cardinality corrections that improve the execution plan without changing the SQL text |
Practice Questions
Practice 1. What is the difference between AWR and ASH in Oracle performance diagnostics?
Practice 2. An execution plan shows E-Rows = 5 but A-Rows = 18,400. What does this indicate and what should you do?
Practice 3. Why is ASH useful for diagnosing a performance spike that lasted only two minutes and has already resolved?
Practice 4. What is a SQL Profile and when is it preferable to a hint?
Practice 5. The AWR Top 5 Wait Events shows enq: TX - row lock contention at 65%. What type of problem does this point to?
Quiz
Quiz 1. You need to investigate a performance problem that occurred between 08:00 and 09:00 yesterday. Which Oracle tool gives you the most relevant aggregate data for that window?
Quiz 2. In V$SQL, elapsed_time for a statement is 4,500,000 and executions is 900. What is the average elapsed time per execution in seconds?
Quiz 3. What does ALLSTATS LAST show in a DBMS_XPLAN.DISPLAY_CURSOR output that a regular EXPLAIN PLAN does not?
Quiz 4. The SQL Tuning Advisor recommends accepting a SQL Profile. You cannot modify the application SQL. Should you accept it?
Quiz 5. AWR and the SQL Tuning Advisor both require Oracle licences. Which licence covers them?
Next up — Data Pump — How to export and import Oracle data and schema objects using expdp and impdp for migrations, cloning, and archiving.