最近自己手写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文本的详细解释,方便大家使用。
自动抓取 最近 3 小时AWR 快照区间,若无 3 小时快照则自动切换前一个 3 小时窗口;
对比起始快照、结束快照,算出该时间段内 增量等待事件指标;
排除空闲等待,额外增加 CPU 时间作为对比项;
按总等待时长降序取 TOP5 瓶颈事件;
以 CSV 格式输出起止时间、等待名、等待次数、总等待秒、单次平均等待毫秒、占 DB 时间百分比、等待分类,方便导出 Excel 做性能报表;
核心定位数据库瓶颈:是 IO 等待、锁等待、网络等待还是 CPU 瓶颈。
set lines 400 -- 单行显示宽度400字符,防止列折行 set pages 1000 -- 每页显示1000行,减少分页空行 set feedback off -- 关闭PL/SQL执行完成的"PL/SQL procedure successfully completed"提示 set serverout on -- 开启dbms_output打印输出,否则看不到CSV结果
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;
3/24 = 3 小时;
优先采集 最近 3 小时负载;
如果 AWR 快照采样间隔长、或刚重启数据库,3 小时内无快照,捕获
no_data_found异常,自动切换到
前一天同 3 小时窗口;
强制快照时间晚于实例启动时间,避免增量计算出现负数等待时间。
select chr(34)||to_char(...)||chr(34) into vstarttime
chr(34)是双引号
",CSV 标准格式,防止时间含空格导致分列错乱。
DBA_HIST_SYSTEM_EVENT B(+) 起始快照 / DBA_HIST_SYSTEM_EVENT E 结束快照采用左外连接拿基线快照等待总量,
计算
区间增量:WAITS = E.TOTAL_WAITS_FG - NVL(B.TOTAL_WAITS_FG,0):该时段总等待次数;
TIME = (E.TIME_WAITED_MICRO_FG - NVL(B.TIME_WAITED_MICRO_FG,0)) / 1000000:总等待秒;
AVGWT = TIME / WAITS * 1000:单次等待平均毫秒;
PCTWTT:该事件等待时间占整个区间
总 DB Time 百分比;
分母:结束快照 DB Time − 起始快照 DB Time(来自
DBA_HIST_SYS_TIME_MODEL);
直观看出哪个等待吃掉数据库绝大部分时间;
过滤
E.WAIT_CLASS != 'Idle':剔除空闲等待(如 sqlnet message from client),只看业务瓶颈等待。
DBA_HIST_SYS_TIME_MODEL取出
DB CPU增量,伪装成一条 “等待事件”:把 CPU 消耗和 IO / 锁等待放在同一报表对比;
如果
PCTWTTCPU 占比很高,说明瓶颈在 CPU;
如果 IO、buffer 等待占比高,瓶颈在磁盘 IO / 内存。
ORDER BY TIME DESC, WAITS DESC WHERE ROWNUM <= 5
按总等待时长降序,只输出消耗时间最多 5 个瓶颈点,快速定位 TOP 性能瓶颈。
for c1 in (...) loop dbms_output.put_line(多字段逗号拼接); end loop;
输出字段格式(CSV):
"起始时间","结束时间","事件名","等待次数","总等待秒","单次平均等待ms","占DBTime百分比","等待分类"
---填充,避免 CSV 空单元格解析异常;db file sequential read高:大量单行随机 IO,索引选择性差;db file scattered read高:全表扫描、多块顺序读;enqueue 类等待:锁冲突、并发更新热点行;
自动容错:无近期快照自动切前一日窗口,不会直接报错中断。
*_FG:Foreground 前台进程指标,排除后台进程等待,更贴合业务负载;DELTA增量通过
前后快照差值计算,而非累计值,精准反映窗口内负载;instance_number隔离各实例数据,多节点不会混算。仅输出 TOP5 事件,如需全量等待事件需去掉
ROWNUM<=5;
仅 3 小时窗口,无法自定义时长;
仅输出汇总等待事件,不细化到 SQL、会话级;
依赖 AWR 快照,实时故障排查需换 V$ 动态视图版本。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/12798004/viewspace-3175461/ ,如需转载,请注明出处,否则将追究法律责任。