Skip to content
Home » All Posts » Oracle AWR ASH Performance Tuning: A Practical Guide to Slow SQL Diagnosis

Oracle AWR ASH Performance Tuning: A Practical Guide to Slow SQL Diagnosis

Introduction: Why Oracle AWR ASH Performance Tuning Matters for Slow SQL

When a business-critical query suddenly runs in minutes instead of seconds, I’ve found that guesswork is the fastest way to waste time. Oracle AWR ASH performance tuning gives me the hard evidence I need: what the database was doing, which SQL was on top, and exactly what it was waiting on.

AWR (Automatic Workload Repository) captures snapshots of database performance over time. ASH (Active Session History) zooms in on what active sessions were doing at precise moments. Together, they answer questions I always need in a slow SQL incident:

  • Which SQL statements are consuming the most resources? (CPU, I/O, logical reads)
  • What wait events dominate during the slowdown? (I/O, locks, latches, network)
  • Is this a system-wide issue or a single bad query?

In my experience, the biggest breakthrough happens when I stop tuning blindly and start from AWR and ASH reports. They show me the top SQL, key wait events, and contention patterns so I can focus on the real root cause instead of tweaking random parameters. This guide walks through how I use these tools in practice to diagnose and fix slow SQL, step by step.

AWR vs ASH: How These Oracle Performance Diagnostics Work Together

When I’m called in to troubleshoot slow SQL, my first decision is always: do I look at the big picture or zoom into a specific moment? That’s exactly how I think about Oracle AWR ASH performance tuning—AWR gives me the wide-angle view, ASH gives me the microscope.

What AWR Captures: The Big-Picture Trend View

AWR (Automatic Workload Repository) stores periodic snapshots of database performance: system statistics, wait events, top SQL, and instance-level load. In practice, I use AWR when I want to see:

  • How performance changed over time (e.g., before vs during a slowdown)
  • Top resource-consuming SQL over a snapshot interval
  • System-wide bottlenecks like I/O, CPU, or buffer busy waits

For example, if a nightly batch suddenly doubles in duration, I generate an AWR report for that window and compare it with a “good” day. This usually highlights which SQL or wait class exploded in cost.

AWR vs ASH: How These Oracle Performance Diagnostics Work Together - image 1

What ASH Captures: The Session-Level Detail

ASH (Active Session History) samples active sessions every second and records what each session is doing: SQL ID, wait event, blocking session, and more. I reach for ASH when I need:

  • Per-session insight into which queries were blocked or waiting
  • Time-sliced analysis of a specific 5–10 minute incident
  • Detailed contention patterns (e.g., hot blocks, row-level locking)

One thing I learned the hard way is that ASH is ideal for “what exactly was happening at 10:05?” while users were complaining, especially if the problem vanished before I could log in.

When to Use AWR, When to Use ASH – and Why You Need Both

In real investigations, I rarely use AWR or ASH alone. My usual flow is:

  • Start with AWR to identify the worst SQL, top waits, and the timeframe where performance degraded.
  • Drill into ASH for that timeframe to see which sessions ran that SQL, what they waited on, and who blocked whom.
  • Validate the root cause by correlating AWR trends with ASH session patterns.

This combined approach keeps me from over-tuning a single query when the real issue is systemic, like undersized I/O or a surge in concurrency. For anyone serious about tuning slow SQL on Oracle, mastering how AWR and ASH complement each other is non-negotiable. DBMS_WORKLOAD_REPOSITORY – Oracle Database PL/SQL Packages and Types Reference

Reading AWR Reports: Finding Slow SQL and Top Wait Events Quickly

When I first started with Oracle AWR ASH performance tuning, AWR reports felt overwhelming—dozens of pages of stats and ratios. Over time, I learned that for slow SQL diagnosis, only a handful of sections really matter. If I follow them in order, I can usually pinpoint the culprit in a few minutes.

Step 1: Check Load Profile and Top Timed Events

I always begin near the top of the AWR report with the Load Profile and Top Timed Events (or Wait Classes in newer versions). Here’s what I look for:

  • DB Time, DB CPU: Is the instance under much heavier load than usual for this period?
  • Transactions and Calls per second: Did workload spike or is the same workload suddenly slower?
  • Top Timed Events / Wait Classes: Are we dominated by CPU, I/O waits (like db file sequential read / scattered read), or concurrency (e.g., enq: TX, buffer busy waits)?

If I see a sudden jump in I/O waits while users report slowness, I already know I’m likely dealing with a physical read or storage bottleneck, and the slow SQL will probably show high disk reads.

Step 2: Use Top SQL Sections to Isolate the Worst Statements

Once I understand the overall bottleneck, I jump straight to the Top SQL sections. For slow SQL, these are my usual priorities:

  • SQL ordered by Elapsed Time: My primary list of suspects. I look for SQL with high elapsed time, executions, and average time per exec.
  • SQL ordered by CPU Time: Helps me distinguish CPU-bound queries from I/O or wait-bound ones.
  • SQL ordered by Gets / Reads: Often highlights inefficient plans doing excessive logical or physical I/O.

From experience, I’ve learned to ignore tiny, one-off statements with high elapsed time but very low impact on overall DB time. Instead, I focus on queries that are both expensive and frequent, or infrequent but dominating elapsed time during the problem window.

For each candidate SQL, I capture the SQL ID and note:

  • Total elapsed time vs. executions (is it slow per execution or just run very often?)
  • Buffer gets and disk reads (is it doing a lot of work?)
  • Parsing issues (many parses may hint at bad application design)

This gives me a short list of SQL IDs to drill into further, either with ASH or by pulling the execution plan.

Step 3: Correlate Slow SQL with Wait Events and Segments

After I’ve identified the likely slow SQL, I go back to the AWR report to see how it ties into the observed waits and where contention is happening.

  • Instance Activity Stats: I check metrics like physical reads, physical writes, parse counts, and hard parses to see if the slow period involved unusual activity patterns.
  • Wait Event Statistics: I compare the Total Wait Time and Waits for critical events, making sure they match the top SQL behavior (e.g., a query with high reads during a spike in I/O waits).
  • Segments by Logical Reads / Physical Reads / Buffer Busy Waits: These sections often show which tables or indexes are hot. If a specific index shows very high logical reads or buffer busy waits, I know to examine access paths in the suspect SQL.

Here’s a simple pattern I use in practice:

  • Identify top SQL by elapsed time and reads.
  • Confirm the same time window and wait classes in the Top Timed Events.
  • Check top segments to see if a particular table or index is overloaded.

If all three line up—one SQL ID, a dominant wait event, and a small set of hot segments—I have a clear tuning target. From there, I’ll jump into ASH or plan analysis and, if needed, reference deeper Oracle guidance on interpreting AWR statistics and wait events Monitoring Performance – Oracle Help Center.

Using ASH to Drill Into Problem SQL, Sessions and Wait Events

Once AWR tells me which time window and SQL IDs look suspicious, I switch to ASH to answer the deeper questions: who was running that SQL, what they were waiting on, and how contention actually played out. This is where Oracle AWR ASH performance tuning moves from theory to concrete, session-level evidence.

Using ASH to Drill Into Problem SQL, Sessions and Wait Events - image 1

Drilling into Problem SQL with ASH Samples

My starting point is usually the SQL ID I captured from AWR. With ASH, I can see how often that SQL showed up as active and what it was doing during the slow period. In real incidents, I often run a query like this against DBA_HIST_ACTIVE_SESS_HISTORY (for historical ASH):

SELECT sample_time,
       session_id,
       session_serial#,
       sql_id,
       wait_class,
       event,
       time_waited
  FROM dba_hist_active_sess_history
 WHERE sql_id = '&sql_id'
   AND sample_time BETWEEN TO_DATE('&begin_time', 'YYYY-MM-DD HH24:MI')
                       AND TO_DATE('&end_time', 'YYYY-MM-DD HH24:MI')
 ORDER BY sample_time;

I’m looking for patterns such as:

  • Long stretches where the same SQL ID appears active for many samples.
  • Consistently high wait_class or specific event values tied to this SQL.
  • Multiple sessions concurrently running the same expensive SQL.

In my experience, this view quickly confirms whether the SQL in question really dominated the slowdown or if it was just one of many contributors.

Finding Problem Sessions, Blockers and Contention Hotspots

After I’ve confirmed the SQL is a genuine problem, I usually pivot to session-level analysis. I want to know which sessions were stuck, who they were blocked by, and whether the issue was localized or widespread. A typical approach I use is:

SELECT sample_time,
       session_id,
       session_serial#,
       session_type,
       user_id,
       sql_id,
       blocking_session,
       event,
       wait_class
  FROM dba_hist_active_sess_history
 WHERE sample_time BETWEEN TO_DATE('&begin_time', 'YYYY-MM-DD HH24:MI')
                       AND TO_DATE('&end_time', 'YYYY-MM-DD HH24:MI')
   AND (wait_class = 'Concurrency' OR wait_class = 'Application')
 ORDER BY sample_time;

This helps me spot:

  • Blocking sessions that repeatedly appear in blocking_session.
  • Lock chains where many sessions wait on the same blocker.
  • Whether the slowdown is driven by row-level locks, enqueue waits, or general concurrency issues.

One thing I learned on a tough production incident was that tuning the “slow” query did nothing until I identified the real blocker through ASH and fixed the locking pattern in the application.

Analyzing Wait Events in ASH to Confirm the Root Cause

Finally, I use ASH to verify that the dominant waits seen in AWR actually match what active sessions experienced. This correlation keeps me from chasing misleading statistics. A simple wait-focused ASH query I rely on is:

SELECT wait_class,
       event,
       COUNT(*) AS samples,
       ROUND(COUNT(*) * 1.0 / SUM(COUNT(*)) OVER () * 100, 2) AS pct_samples
  FROM dba_hist_active_sess_history
 WHERE sample_time BETWEEN TO_DATE('&begin_time', 'YYYY-MM-DD HH24:MI')
                       AND TO_DATE('&end_time', 'YYYY-MM-DD HH24:MI')
 GROUP BY wait_class, event
 ORDER BY samples DESC;

This tells me which wait events consumed the majority of active time in the exact problem window. I then cross-check:

  • Do these waits align with the top SQL identified in AWR?
  • Are they concentrated on a few sessions or spread across many?
  • Are we seeing a classic pattern (e.g., I/O bound, latch contention, log file sync, row locking)?

In my day-to-day tuning work, this ASH drill-down turns vague complaints like “the database was slow” into precise statements such as “between 10:02 and 10:08, sessions running SQL ID X spent 80% of their time waiting on db file sequential read against table Y’s primary key index.” That level of clarity is what makes Oracle AWR ASH performance tuning genuinely powerful.

Diagnosing Lock and Latch Contention from AWR and ASH

Some of the most painful slowdowns I’ve seen weren’t caused by bad plans or I/O, but by hidden lock and latch contention. The good news is that with Oracle AWR ASH performance tuning, these patterns are usually very visible if I know where to look in both AWR and ASH.

Spotting Concurrency Problems in AWR

In AWR, I first scan the Top Timed Events and Wait Classes for concurrency-related waits:

  • Wait class = Concurrency or Application
  • Events like enq: TX – row lock contention, library cache lock, cursor: pin S, or latch-related waits

Next, I check Instance Activity Stats for spikes in enqueue requests and failures, and Segments by Buffer Busy Waits / ITL Waits to see if specific tables or indexes are hot. When those sections light up together, I know I’m dealing with blocking, hot blocks, or latch pressure rather than just slow I/O.

Using ASH to Identify Blockers and Lock Chains

Once AWR confirms a concurrency issue, I rely on ASH to identify exactly who is blocking whom. A query I often use against historical ASH is:

SELECT sample_time,
       session_id,
       session_serial#,
       sql_id,
       event,
       blocking_session,
       blocking_session_serial#
  FROM dba_hist_active_sess_history
 WHERE wait_class IN ('Concurrency','Application')
   AND sample_time BETWEEN TO_DATE('&begin_time','YYYY-MM-DD HH24:MI')
                       AND TO_DATE('&end_time','YYYY-MM-DD HH24:MI')
 ORDER BY sample_time;

In my experience, a few patterns stand out quickly:

  • The same blocking_session appears for many samples and many sessions.
  • Most waits are enq: TX – row lock contention on a particular DML-heavy table.
  • Sessions running a specific SQL ID are consistently victims, not blockers.

That lets me focus on the blocking SQL and its transaction design instead of endlessly tuning the victim queries.

Recognizing Latch and Hot-Block Contention

Latches and hot blocks are subtler but still visible with AWR and ASH. In AWR, I look for:

  • Latch Free or specific latch waits in Top Timed Events.
  • High values in Latch Statistics sections (if included).
  • Buffer busy waits and high logical reads on a few segments.

Then, I use ASH to see which objects and operations are involved. A simple object-level breakdown might look like:

SELECT owner, object_name, object_type,
       COUNT(*) AS samples
  FROM dba_hist_active_sess_history h,
       dba_objects o
 WHERE h.current_obj# = o.object_id
   AND h.wait_class = 'Concurrency'
   AND h.sample_time BETWEEN TO_DATE('&begin_time','YYYY-MM-DD HH24:MI')
                          AND TO_DATE('&end_time','YYYY-MM-DD HH24:MI')
 GROUP BY owner, object_name, object_type
 ORDER BY samples DESC;

This quickly shows me if a single index or table is at the center of contention. One thing I’ve learned over time is that these issues are often fixed not by “faster SQL,” but by redesigning hot rows, splitting workloads, or revisiting how frequently certain rows or lookup tables are updated.

From Data to Action: Tuning Slow SQL Based on AWR and ASH Evidence

The real payoff of Oracle AWR ASH performance tuning is turning all that diagnostic data into targeted fixes. When I’m on a live incident, I always ask: given what AWR and ASH are telling me, what is the smallest, safest change that will reduce DB time the most?

From Data to Action: Tuning Slow SQL Based on AWR and ASH Evidence - image 1

Translating AWR Top SQL into Query and Index Changes

Once AWR has highlighted the top SQL by elapsed time and reads, my next step is to pull execution plans and align them with what I saw in AWR/ASH. For a suspect SQL ID, I often grab the plan like this:

SELECT *
  FROM table(dbms_xplan.display_awr('&sql_id'));

Based on the plan and AWR metrics, typical actions I’ve taken include:

  • Adding or adjusting indexes when I see full table scans with high physical reads and poor selectivity.
  • Rewriting predicates or joins to be sargable (e.g., avoid functions on indexed columns) when logical reads are huge.
  • Stabilizing execution plans with SQL Plan Baselines when plans keep flipping between good and bad versions.

In my experience, one well-chosen index or predicate rewrite, guided by AWR and ASH evidence, often beats broad configuration tweaks.

Using ASH Patterns to Fix Concurrency and Hot-Object Issues

ASH tells me which sessions and objects are really suffering. When I see heavy enq: TX – row lock contention or hot blocks focused on a single table or index, I consider:

  • Changing transaction design: commit more frequently, avoid long-running “batch-in-one-transaction” patterns.
  • Reducing hot-row contention: spread updates across multiple rows/partitions instead of hammering a single counter row.
  • Rebalancing access paths: adding or modifying indexes so different workloads hit different structures instead of one hot index.

One thing I’ve learned is that ASH-based evidence makes it much easier to convince application teams to change their patterns, because I can show exactly which SQL and rows are blocking others.

Targeted Configuration and Resource Changes (Not Guesswork)

Only after exhausting SQL and schema fixes do I look at configuration. AWR’s DB Time, CPU usage, and I/O waits, combined with ASH’s active session counts, guide me to:

  • Increase CPU capacity or adjust resource manager settings when the system is clearly CPU-bound.
  • Tune memory settings (PGA/SGA) when I see excessive sorts to disk or buffer cache misses tied to key queries.
  • Work with storage teams when AWR/ASH show persistent I/O saturation across many queries, not just one.

To avoid random parameter changes, I like to cross-check my ideas against solid references on Oracle optimizer and memory tuning 10 Optimizer Statistics Concepts – Oracle Help Center. Over time, this evidence-driven approach has given me far more predictable results than tweaking settings based on rules of thumb.

Common Pitfalls in Oracle AWR ASH Performance Tuning

When I started using AWR and ASH, I spent a lot of time chasing the wrong problems. Over time, I’ve seen the same mistakes repeated across teams working on Oracle AWR ASH performance tuning.

The most common pitfall is focusing on ratios and summary scores instead of DB Time, top waits, and top SQL. Pretty numbers like buffer cache hit ratio can look bad while the real issue is a single blocking transaction. Another frequent trap is ignoring the time window: comparing a “bad” report to no baseline, or mixing peak-hour behavior with quiet periods, leads to false conclusions.

I also see people tuning victims instead of root causes—optimizing queries that are simply blocked, not inherently slow. ASH’s blocking_session columns usually reveal this, but they’re easy to overlook. Finally, it’s easy to overreact to one-off spikes; I try to confirm that a pattern is repeatable before making invasive schema or configuration changes. Being aware of these pitfalls has saved me from a lot of wasted tuning effort.

Conclusion and Next Steps for Better Oracle AWR ASH Performance Tuning

Over the years, I’ve found that consistent, repeatable habits matter far more than exotic tricks in Oracle AWR ASH performance tuning. If I stick to a simple workflow, I can usually turn vague complaints about slowness into concrete, data-backed actions.

Conclusion and Next Steps for Better Oracle AWR ASH Performance Tuning - image 1

My go-to diagnostic loop looks like this:

  • Use AWR to pick the right time window and confirm whether the instance is CPU, I/O, or concurrency bound.
  • Identify top SQL by elapsed time, CPU, and reads, and note the key SQL IDs.
  • Drill into ASH for those SQL IDs and time windows to see sessions, waits, and hot objects.
  • Translate patterns into targeted changes: SQL rewrites, new or adjusted indexes, better transaction design, and only then configuration tweaks.

What helped me most was practicing this loop regularly, even on non-critical issues, so it became second nature before real incidents hit. As a next step, you can script a few of the ASH and AWR queries you use most often, or wrap them in simple reports, so your tuning workflow becomes faster and more consistent across your team.

Join the conversation

Your email address will not be published. Required fields are marked *