Sunday, August 10, 2025

ASH & AWR Performance Tuning Pack

ASH & AWR Performance Tuning Pack (RAC-aware)

ASH & AWR Performance Tuning Pack

A RAC-aware script for Oracle DBAs (11gR2–19c+)

This script collection provides a powerful toolkit for diagnosing performance issues in Oracle databases using the Active Session History (ASH) and Automatic Workload Repository (AWR). It's designed to be RAC-aware and works on versions from 11gR2 to 19c and beyond.

Requirements: SELECT_CATALOG_ROLE. AWR queries require the Oracle Diagnostics Pack license.

set pages 200 lines 200 trimspool on long 100000 longchunksize 100000
col event format a50 trunc
col wait_class format a20 trunc
col username format a20
col module format a35 trunc
col sql_text format a80 trunc
col object_name format a40 trunc
col object_type format a18 trunc
col segment_name format a35 trunc
set verify off

-- ---- Parameters (adjust as you like)
DEF mins_back = 60
DEF days_back = 1
DEF top_n     = 5
DEF hour_from = 9
DEF hour_to   = 11
DEF start_ts  = '2012-11-14 22:00'
DEF end_ts    = '2012-11-14 23:00'
DEF sqlid     = '&&sqlid'      -- set when needed

1. Top Recent Wait Events (ASH)

Shows the top wait events from the last &mins_back minutes.

WITH ash AS (
  SELECT /*+ MATERIALIZE */
         event, wait_class,
         (wait_time + time_waited) AS wt_us
  FROM   gv$active_session_history
  WHERE  sample_time >= systimestamp - ( &mins_back / (24*60) )
    AND  event IS NOT NULL
)
SELECT * FROM (
  SELECT event,
         wait_class,
         ROUND(SUM(wt_us)/1e6,2) AS total_wait_s,
         ROUND(100*SUM(wt_us)/NULLIF(SUM(SUM(wt_us)) OVER (),0),1) AS pct
  FROM   ash
  GROUP  BY event, wait_class
  ORDER  BY total_wait_s DESC
)
FETCH FIRST &top_n ROWS ONLY;

2. Top Wait Class Since Startup

Aggregates wait times by class across the entire system since the last startup.

SELECT wait_class,
       SUM(time_waited) AS time_waited_cs,
       ROUND(SUM(time_waited)/100,1) AS time_waited_s
FROM   v$system_event e JOIN v$event_name n ON n.event_id = e.event_id
WHERE  n.wait_class <> 'Idle'
GROUP  BY wait_class
ORDER  BY time_waited_cs DESC;

3. Users Currently Waiting (non-Idle)

Lists active user sessions that are currently in a non-idle wait state.

SELECT s.inst_id, s.sid, s.serial#, s.username, s.module,
       s.event, s.wait_class, s.state, s.seconds_in_wait
FROM   gv$session s
WHERE  s.type = 'USER'
  AND  s.username IS NOT NULL
  AND  s.state = 'WAITING'
  AND  s.wait_class <> 'Idle'
ORDER  BY s.seconds_in_wait DESC;

4. Main DB Wait Events in a Time Interval (AWR)

First, find the snapshot range for your desired time window.

SELECT MIN(snap_id) begin_snap, MAX(snap_id) end_snap,
       TO_CHAR(MIN(begin_interval_time),'YYYY-MM-DD HH24:MI') begin_time,
       TO_CHAR(MAX(end_interval_time) ,'YYYY-MM-DD HH24:MI') end_time
FROM   dba_hist_snapshot
WHERE  end_interval_time BETWEEN TO_DATE('&&start_ts','YYYY-MM-DD HH24:MI')
                         AND TO_DATE('&&end_ts','YYYY-MM-DD HH24:MI');

Then, define begin_snap and end_snap and run the query below to see top events in that range.

-- DEF begin_snap = 12345
-- DEF end_snap   = 12350
-- SELECT * FROM (
--   SELECT h.event, h.wait_class,
--          ROUND(SUM(h.time_waited)/1e6,2) total_wait_s
--   FROM   dba_hist_active_sess_history h
--   WHERE  h.snap_id BETWEEN &begin_snap AND &end_snap
--     AND  h.event IS NOT NULL
--   GROUP  BY h.event, h.wait_class
--   ORDER  BY total_wait_s DESC
-- ) FETCH FIRST &top_n ROWS ONLY;

5. Top CPU-consuming SQL in a Time Window (AWR)

Identifies the most CPU-intensive SQL statements between specific hours over the last &days_back days.

SELECT * FROM (
  SELECT a.sql_id,
         ROUND(SUM(a.cpu_time_delta)/1e6,2) cpu_s,
         ROUND(SUM(a.elapsed_time_delta)/1e6,2) ela_s,
         SUM(a.executions_delta) execs
  FROM   dba_hist_sqlstat a
  JOIN   dba_hist_snapshot s ON s.snap_id = a.snap_id
  WHERE  s.begin_interval_time >= TRUNC(SYSDATE) - &days_back
    AND  EXTRACT(HOUR FROM s.end_interval_time) BETWEEN &hour_from AND &hour_to
  GROUP  BY a.sql_id
  ORDER  BY cpu_s DESC
) FETCH FIRST &top_n ROWS ONLY;

6. Objects with Most Waits in Past Hour (ASH)

Pinpoints which database objects (tables, indexes) have been sources of contention recently.

SELECT * FROM (
  SELECT o.owner||'.'||o.object_name AS object_name,
         o.object_type,
         a.event,
         ROUND(SUM(a.wait_time + a.time_waited)/1e6,2) AS total_wait_s
  FROM   gv$active_session_history a
  JOIN   dba_objects o ON o.object_id = a.current_obj#
  WHERE  a.sample_time BETWEEN SYSTIMESTAMP - (1/24) AND SYSTIMESTAMP
    AND  a.event IS NOT NULL
  GROUP  BY o.owner, o.object_name, o.object_type, a.event
  ORDER  BY total_wait_s DESC
) FETCH FIRST &top_n ROWS ONLY;

7. Top Segments by Physical Reads

Shows which segments are responsible for the most physical I/O.

SELECT * FROM (
  SELECT owner||'.'||object_name AS segment_name,
         object_type,
         value AS total_physical_reads
  FROM   v$segment_statistics
  WHERE  statistic_name = 'physical reads'
  ORDER  BY total_physical_reads DESC
) FETCH FIRST &top_n ROWS ONLY;

8. Top SQL (ASH) in Past Hour

Finds the SQL statements with the highest total wait time in the last hour.

SELECT * FROM (
  SELECT a.sql_id,
         u.username,
         ROUND(SUM(a.wait_time + a.time_waited)/1e6,2) AS total_wait_s,
         MIN(sa.sql_text) KEEP (DENSE_RANK FIRST ORDER BY SUM(a.wait_time + a.time_waited) DESC) AS sql_text
  FROM   gv$active_session_history a
  LEFT   JOIN v$sqlarea sa ON sa.sql_id = a.sql_id
  LEFT   JOIN dba_users u  ON u.user_id = a.user_id
  WHERE  a.sample_time >= SYSTIMESTAMP - (1/24)
    AND  a.sql_id IS NOT NULL
  GROUP  BY a.sql_id, u.username
  ORDER  BY total_wait_s DESC
) FETCH FIRST &top_n ROWS ONLY;

9. SQL with Highest I/O (reads) Past Day (AWR)

Identifies SQL that spent the most time on I/O wait events in the last 24 hours.

SELECT * FROM (
  SELECT h.sql_id,
         COUNT(*) AS ash_secs
  FROM   dba_hist_active_sess_history h
  JOIN   dba_hist_snapshot x
         ON x.snap_id = h.snap_id
        AND x.dbid = h.dbid
        AND x.instance_number = h.instance_number
  WHERE  x.begin_interval_time > SYSDATE - 1
    AND  h.event IN ('db file sequential read','db file scattered read')
  GROUP  BY h.sql_id
  ORDER  BY ash_secs DESC
) FETCH FIRST &top_n ROWS ONLY;

10. Top CPU since Past Day (AWR)

Shows the top CPU consumers from the last day, along with their disk reads and execution counts.

SELECT * FROM (
  SELECT a.sql_id,
         ROUND(SUM(a.cpu_time_delta)/1e6,2) cpu_s,
         ROUND(SUM(a.disk_reads_delta),0) disk_reads,
         SUM(a.executions_delta) execs
  FROM   dba_hist_sqlstat a
  JOIN   dba_hist_snapshot s ON s.snap_id = a.snap_id
  WHERE  s.begin_interval_time > SYSDATE - 1
  GROUP  BY a.sql_id
  ORDER  BY cpu_s DESC
) FETCH FIRST &top_n ROWS ONLY;

11. Top SQL at a Reported Hour

First, find the specific snapshot ID for the hour you want to investigate.

SELECT snap_id, TO_CHAR(begin_interval_time,'YYYY-MM-DD HH24:MI') begin_time,
       TO_CHAR(end_interval_time,'YYYY-MM-DD HH24:MI') end_time
FROM   dba_hist_snapshot
WHERE  end_interval_time BETWEEN TO_DATE('&&start_ts','YYYY-MM-DD HH24:MI')
                         AND TO_DATE('&&end_ts','YYYY-MM-DD HH24:MI')
ORDER  BY snap_id;

Then, define the snapid and run the query below to see the top SQL by disk reads for that snapshot.

-- After you pick a specific &snapid:
-- SELECT * FROM (
--   SELECT sql.sql_id,
--          sql.buffer_gets_delta  buf_gets,
--          sql.disk_reads_delta   disk_reads,
--          ROUND(sql.iowait_delta/1e6,2) io_wait_s,
--          ROUND(sql.cpu_time_delta/1e6,2) cpu_s,
--          ROUND(sql.elapsed_time_delta/1e6,2) ela_s
--   FROM   dba_hist_sqlstat sql
--   WHERE  sql.snap_id = &snapid
--   ORDER  BY disk_reads DESC
-- ) FETCH FIRST &top_n ROWS ONLY;

12. Trends for One SQL_ID (last day)

Tracks the performance metrics of a specific sql_id over the last day, snapshot by snapshot.

SELECT s.snap_id,
       TO_CHAR(s.begin_interval_time,'YYYY-MM-DD HH24:MI') AS begin_time,
       sql.executions_delta   AS execs,
       sql.buffer_gets_delta  AS buffer_gets,
       sql.disk_reads_delta   AS disk_reads,
       ROUND(sql.iowait_delta/1e6,2) AS io_wait_s,
       ROUND(sql.cpu_time_delta/1e6,2) AS cpu_s,
       ROUND(sql.elapsed_time_delta/1e6,2) AS ela_s
FROM   dba_hist_sqlstat sql
JOIN   dba_hist_snapshot s ON s.snap_id = sql.snap_id
WHERE  s.begin_interval_time > SYSDATE - 1
  AND  sql.sql_id = '&&sqlid'
ORDER  BY s.begin_interval_time;

13. Plans observed for SQL_ID

Checks if a specific SQL statement is "flipping" between different execution plans, which can cause performance instability.

SELECT sql_id,
       plan_hash_value,
       SUM(executions_delta) AS executions,
       SUM(rows_processed_delta) AS rows_processed,
       TRUNC(SUM(cpu_time_delta)/1e6/60) AS cpu_mins,
       TRUNC(SUM(elapsed_time_delta)/1e6/60) AS ela_mins
FROM   dba_hist_sqlstat
WHERE  sql_id = '&&sqlid'
GROUP  BY sql_id, plan_hash_value
ORDER  BY cpu_mins DESC;

14. Top 5 SQL by ADDM Benefit (last 7 days)

Lists the SQL statements that the Automatic Database Diagnostic Monitor (ADDM) has identified as having the most potential for improvement.

SELECT * FROM (
  SELECT b.attr1 AS sql_id, MAX(a.benefit) AS benefit
  FROM   dba_advisor_recommendations a
  JOIN   dba_advisor_objects b
    ON   a.task_id = b.task_id AND a.rec_id = b.object_id
  WHERE  a.task_id IN (
          SELECT DISTINCT t.task_id
          FROM   dba_advisor_tasks t
          JOIN   dba_advisor_log   l ON l.task_id = t.task_id AND l.status='COMPLETED'
          WHERE  t.advisor_name = 'ADDM'
            AND  t.created >= SYSDATE - 7
         )
    AND  LENGTH(b.attr4) > 1
  GROUP  BY b.attr1
  ORDER  BY MAX(a.benefit) DESC
) FETCH FIRST 5 ROWS ONLY;

No comments:

Post a Comment