ITPub博客

首页 > 数据库 > Oracle > 第68期 自己手写Oracle AWR 近 3 小时 TOP5 非空闲等待事件采集脚本(一)

第68期 自己手写Oracle AWR 近 3 小时 TOP5 非空闲等待事件采集脚本(一)

原创 Oracle 作者:pxbibm 时间:2026-07-08 16:14:44 0 删除 编辑

最近自己手写Oracle AWR 近 3 小时 TOP5 非空闲等待事件采集脚本,用于日常做数据库维护时的小工具,具体的sql如下:

set lines 400
set pages 1000
set feedback off
set serverout on
declare
vevent varchar2(100);  
vtime number;
vavgtime number;
vpctwt number;
vwaits number;
vwaitclass varchar2(100);
vbid number ;
veid number ;
vdbid number ;
vinid number ;
startid number;
endid number;
vstarttime varchar2(200);
vendtime varchar2(200);
vdbname varchar2(36);
v_inst_startup date;
begin 
select instance_number,STARTUP_TIME into vinid ,v_inst_startup from v$instance;
select dbid into vdbid from v$database;
--
     begin
         select min(snap_id),max(snap_id) into vbid,veid
         from dba_hist_snapshot
         where END_INTERVAL_TIME between  sysdate - 3/24 
                                     and  sysdate
           and END_INTERVAL_TIME > = v_inst_startup
           and instance_number = vinid
           and dbid = vdbid;
     exception when no_data_found then
         select min(snap_id),max(snap_id) into vbid,veid
         from dba_hist_snapshot
         where END_INTERVAL_TIME between  sysdate -1
                                     and  sysdate -1 - 3/24
           and END_INTERVAL_TIME > = v_inst_startup
           and instance_number = vinid
           and dbid = vdbid;
     end;
     
     select chr(34)||to_char(a.end_interval_time,'yyyy-mm-dd hh24:mi')||chr(34) into vstarttime from dba_hist_snapshot a where snap_id=vbid and instance_number=vinid;
     select chr(34)||to_char(a.end_interval_time,'yyyy-mm-dd hh24:mi')||chr(34) into vendtime from dba_hist_snapshot a where snap_id=veid and instance_number=vinid;
for c1 in (
SELECT EVENT,
       WAITS,
       round(TIME,0) tm,
       round(DECODE(WAITS,
              NULL,
              TO_NUMBER(NULL),
              0,
              TO_NUMBER(NULL),
              TIME / WAITS * 1000),0) AVGWT,
       round(PCTWTT,2) PCTWTT,
       WAIT_CLASS
  FROM (SELECT EVENT, WAITS, TIME, PCTWTT, WAIT_CLASS
          FROM (SELECT E.EVENT_NAME EVENT,
                       E.TOTAL_WAITS_FG - NVL(B.TOTAL_WAITS_FG, 0) WAITS,
                       (E.TIME_WAITED_MICRO_FG - NVL(B.TIME_WAITED_MICRO_FG, 0)) /
                       1000000 TIME,
                       100 *
                       (E.TIME_WAITED_MICRO_FG - NVL(B.TIME_WAITED_MICRO_FG, 0)) /
                       ((SELECT sum(value)
                           FROM DBA_HIST_SYS_TIME_MODEL e
                          WHERE e.SNAP_ID = veid
                            AND e.INSTANCE_NUMBER = vinid
                            AND e.STAT_NAME = 'DB time') -
                       (SELECT sum(value)
                           FROM DBA_HIST_SYS_TIME_MODEL b
                          WHERE b.SNAP_ID = vbid
                            AND b.INSTANCE_NUMBER = vinid
                            AND b.STAT_NAME = 'DB time')) PCTWTT,
                       E.WAIT_CLASS WAIT_CLASS
                  FROM DBA_HIST_SYSTEM_EVENT B, DBA_HIST_SYSTEM_EVENT E
                 WHERE B.SNAP_ID(+) = vbid
                   AND E.SNAP_ID = veid
                   AND B.INSTANCE_NUMBER(+) = vinid
                   AND E.INSTANCE_NUMBER = vinid
                   AND B.EVENT_ID(+) = E.EVENT_ID
                   AND E.TOTAL_WAITS > NVL(B.TOTAL_WAITS, 0)
                   AND E.WAIT_CLASS != 'Idle'
                UNION ALL
                SELECT 'CPU time' EVENT,
                       TO_NUMBER(NULL) WAITS,
                       ((SELECT sum(value)
                           FROM DBA_HIST_SYS_TIME_MODEL e
                          WHERE e.SNAP_ID = veid
                            AND e.INSTANCE_NUMBER = vinid
                            AND e.STAT_NAME = 'DB CPU') -
                       (SELECT sum(value)
                           FROM DBA_HIST_SYS_TIME_MODEL b
                          WHERE b.SNAP_ID = vbid
                            AND b.INSTANCE_NUMBER = vinid
                            AND b.STAT_NAME = 'DB CPU')) / 1000000 TIME,
                       100 * ((SELECT sum(value)
                                 FROM DBA_HIST_SYS_TIME_MODEL e
                                WHERE e.SNAP_ID = veid
                                  AND e.INSTANCE_NUMBER = vinid
                                  AND e.STAT_NAME = 'DB CPU') -
                       (SELECT sum(value)
                                 FROM DBA_HIST_SYS_TIME_MODEL b
                                WHERE b.SNAP_ID = vbid
                                  AND b.INSTANCE_NUMBER = vinid
                                  AND b.STAT_NAME = 'DB CPU')) /
                       ((SELECT sum(value)
                           FROM DBA_HIST_SYS_TIME_MODEL e
                          WHERE e.SNAP_ID = veid
                            AND e.INSTANCE_NUMBER = vinid
                            AND e.STAT_NAME = 'DB time') -
                       (SELECT sum(value)
                           FROM DBA_HIST_SYS_TIME_MODEL b
                          WHERE b.SNAP_ID = vbid
                            AND b.INSTANCE_NUMBER = vinid
                            AND b.STAT_NAME = 'DB time')) PCTWTT,
                       NULL WAIT_CLASS
                  from dual
                 WHERE ((SELECT sum(value)
                           FROM DBA_HIST_SYS_TIME_MODEL e
                          WHERE e.SNAP_ID = veid
                            AND e.INSTANCE_NUMBER = vinid
                            AND e.STAT_NAME = 'DB CPU') -
                       (SELECT sum(value)
                           FROM DBA_HIST_SYS_TIME_MODEL b
                          WHERE b.SNAP_ID = vbid
                            AND b.INSTANCE_NUMBER = vinid
                            AND b.STAT_NAME = 'DB CPU')) > 0)
         ORDER BY TIME DESC, WAITS DESC)
 WHERE ROWNUM <= 5) loop
  dbms_output.put_line(vstarttime||','||vendtime||','||
                           chr(34)||c1.EVENT||chr(34)||','||
                           chr(34)|| nvl(to_char(c1.WAITS),'---')||chr(34)||','||
                           chr(34)|| nvl(to_char(c1.Tm),'---')||chr(34)||','||
                           chr(34)|| nvl(to_char(c1.AVGWT),'---')||chr(34)||','||
                           chr(34)|| nvl(to_char(c1.PCTWTT),'---')||chr(34)||','||
                           chr(34)|| nvl(to_char(c1.WAIT_CLASS),'---')||chr(34));
end loop;    
     
end;
/

下面详细的介绍一下脚本编写的思路和sql文本的详细解释,方便大家使用。

完整脚本逐段拆解:Oracle AWR 近 3 小时 TOP5 非空闲等待事件采集脚本

一、脚本整体用途

  1. 自动抓取 最近 3 小时AWR 快照区间,若无 3 小时快照则自动切换前一个 3 小时窗口;

  2. 对比起始快照、结束快照,算出该时间段内 增量等待事件指标;

  3. 排除空闲等待,额外增加 CPU 时间作为对比项;

  4. 按总等待时长降序取 TOP5 瓶颈事件;

  5. 以 CSV 格式输出起止时间、等待名、等待次数、总等待秒、单次平均等待毫秒、占 DB 时间百分比、等待分类,方便导出 Excel 做性能报表;

  6. 核心定位数据库瓶颈:是 IO 等待、锁等待、网络等待还是 CPU 瓶颈。

二、开头 SQL*Plus 格式化参数

set lines 400       -- 单行显示宽度400字符,防止列折行
set pages 1000      -- 每页显示1000行,减少分页空行
set feedback off    -- 关闭PL/SQL执行完成的"PL/SQL procedure successfully completed"提示
set serverout on    -- 开启dbms_output打印输出,否则看不到CSV结果

三、变量定义区

所有变量用于存储实例、数据库、快照起止 ID、时间字符串:
  • vinid:当前实例号

  • v_inst_startup:实例启动时间(过滤启动前无效快照)

  • vdbid:数据库 DBID

  • vbid:起始快照 SNAP_ID

  • veid:结束快照 SNAP_ID

  • vstarttime/vendtime:格式化快照起止时间(带双引号,适配 CSV)

四、第一块逻辑:获取实例、数据库基础信息

select instance_number,STARTUP_TIME into vinid ,v_inst_startup from v$instance;
select dbid into vdbid from v$database;

作用:区分多实例 RAC 环境,仅统计当前连接实例的 AWR 数据;过滤实例启动前的快照,避免异常负值。

五、核心快照窗口自动适配逻辑

begin
    -- 优先取:当前时间往前3小时区间快照
    select min(snap_id),max(snap_id) into vbid,veid
    from dba_hist_snapshot
    where END_INTERVAL_TIME between  sysdate - 3/24 and  sysdate
      and END_INTERVAL_TIME >= v_inst_startup
      and instance_number = vinid
      and dbid = vdbid;
exception when no_data_found then
    -- 无3小时内快照时,取前一个3小时窗口:昨天同一时段
    select min(snap_id),max(snap_id) into vbid,veid
    from dba_hist_snapshot
    where END_INTERVAL_TIME between  sysdate -1 - 3/24 and  sysdate -1
      and END_INTERVAL_TIME >= v_inst_startup
      and instance_number = vinid
      and dbid = vdbid;
end;
  1. 3/24 = 3 小时;

  2. 优先采集 最近 3 小时负载;

  3. 如果 AWR 快照采样间隔长、或刚重启数据库,3 小时内无快照,捕获 no_data_found异常,自动切换到 前一天同 3 小时窗口;

  4. 强制快照时间晚于实例启动时间,避免增量计算出现负数等待时间。

六、拼接 CSV 时间字段

select chr(34)||to_char(...)||chr(34) into vstarttime

chr(34)是双引号 ",CSV 标准格式,防止时间含空格导致分列错乱。

七、核心查询:计算增量等待事件(UNION 两部分)

子查询 1:DBA_HIST_SYSTEM_EVENT 增量等待事件(非空闲)

数据源: DBA_HIST_SYSTEM_EVENT B(+) 起始快照 / DBA_HIST_SYSTEM_EVENT E 结束快照采用左外连接拿基线快照等待总量, 计算 区间增量:
  1. WAITS = E.TOTAL_WAITS_FG - NVL(B.TOTAL_WAITS_FG,0):该时段总等待次数;
  2. TIME = (E.TIME_WAITED_MICRO_FG - NVL(B.TIME_WAITED_MICRO_FG,0)) / 1000000:总等待秒;
  3. AVGWT = TIME / WAITS * 1000:单次等待平均毫秒;
  4. PCTWTT:该事件等待时间占整个区间 总 DB Time 百分比;
    • 分母:结束快照 DB Time − 起始快照 DB Time(来自 DBA_HIST_SYS_TIME_MODEL);

    • 直观看出哪个等待吃掉数据库绝大部分时间;

  5. 过滤 E.WAIT_CLASS != 'Idle':剔除空闲等待(如 sqlnet message from client),只看业务瓶颈等待。

子查询 2:UNION 追加 CPU time 作为对比项

单独从 DBA_HIST_SYS_TIME_MODEL取出 DB CPU增量,伪装成一条 “等待事件”:
  • 把 CPU 消耗和 IO / 锁等待放在同一报表对比;

  • 如果 PCTWTTCPU 占比很高,说明瓶颈在 CPU;

  • 如果 IO、buffer 等待占比高,瓶颈在磁盘 IO / 内存。

八、排序与截取 TOP5

ORDER BY TIME DESC, WAITS DESC
WHERE ROWNUM <= 5
按总等待时长降序,只输出消耗时间最多 5 个瓶颈点,快速定位 TOP 性能瓶颈。

九、循环输出 CSV 文本

for c1 in (...) loop
  dbms_output.put_line(多字段逗号拼接);
end loop;

输出字段格式(CSV): "起始时间","结束时间","事件名","等待次数","总等待秒","单次平均等待ms","占DBTime百分比","等待分类"

  • 空值用 ---填充,避免 CSV 空单元格解析异常;
  • 所有字符串字段包裹双引号,适配 Excel 导入。

十、脚本业务价值 & 使用场景

  1. 短窗口性能巡检:快速定位近 3 小时突发性能卡顿;
  2. 自动化巡检脚本:可嵌入 shell 定时执行,输出 csv 日志留存;
  3. 区分瓶颈类型:
    • CPU time 占比高:SQL 计算量大、逻辑读高、缺少索引;
    • db file sequential read高:大量单行随机 IO,索引选择性差;
    • db file scattered read高:全表扫描、多块顺序读;
    • enqueue 类等待:锁冲突、并发更新热点行;

  4. 自动容错:无近期快照自动切前一日窗口,不会直接报错中断。

十一、关键知识点补充

  1. *_FG:Foreground 前台进程指标,排除后台进程等待,更贴合业务负载;
  2. DB Time = DB CPU + 非空闲等待总时间,是衡量业务负载核心指标;
  3. 所有 DELTA增量通过 前后快照差值计算,而非累计值,精准反映窗口内负载;
  4. RAC 兼容:通过 instance_number隔离各实例数据,多节点不会混算。

十二、局限性

  1. 仅输出 TOP5 事件,如需全量等待事件需去掉 ROWNUM<=5;

  2. 仅 3 小时窗口,无法自定义时长;

  3. 仅输出汇总等待事件,不细化到 SQL、会话级;

  4. 依赖 AWR 快照,实时故障排查需换 V$ 动态视图版本。



来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/12798004/viewspace-3175461/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
【DBAplus社群】联合发起人。ACOUG核心成员,Oracle用户组年轻专家。熟悉Oracle的体系结构,擅长Oracle数据库架构规划、SQL优化、OWI方面性能的优化。曾服务于航天、航空、机车、大型制造行业数据库的维护以及系统开发工作,开发过基于Oracle数据库的大型ERP系统。
  • 博文量
    341
  • 访问量
    2469360