ITPub博客

首页 > 数据库 > Oracle > 第71期 通过SQL Monitor 实时执行监控抓取TOP N慢SQL

第71期 通过SQL Monitor 实时执行监控抓取TOP N慢SQL

原创 Oracle 作者:pxbibm 时间:2026-07-15 10:55:13 0 删除 编辑

通过SQL Monitor 实时执行监控抓取TOP N慢SQL,首先上SQL,声明一下,这个脚本来自刘永浦老师的脚本,这个脚本使用比较普遍,所以未经刘老师允许,刘老师看见,请见谅,实在是抱歉。

set linesize 300 pagesize 0
SET DEF ^ TERM OFF ECHO ON AUTOP OFF VER OFF SERVEROUT ON SIZE 1000000;
SET TERM ON ECHO OFF;
set trims on
PRO 
PRO  generate top sql monitor html files
PRO
DEF sql_monitor_reports = '$topn';
set term off
SPO _sqlmon.sql;
DECLARE
  l_count NUMBER := 0;
  TYPE mon_rt IS RECORD (
    sql_id varchar2(30),
	elapsed_time number,
    sql_exec_start DATE,
    sql_exec_id NUMBER,
    sql_plan_hash_value NUMBER,
    status varchar2(10),
	username varchar2(30),
	avg_time number,
	con_cnt number );
  mon_rec mon_rt;
  mon_cv SYS_REFCURSOR;
BEGIN
    DBMS_OUTPUT.PUT_LINE('VAR mon_exec_start VARCHAR2(14);');
	DBMS_OUTPUT.PUT_LINE('VAR mon_elapsed_time NUMBER;');
    DBMS_OUTPUT.PUT_LINE('VAR mon_exec_id NUMBER;');
    DBMS_OUTPUT.PUT_LINE('VAR mon_sql_plan_hash_value NUMBER;');
    DBMS_OUTPUT.PUT_LINE('VAR mon_status varchar2(10);');
	DBMS_OUTPUT.PUT_LINE('VAR mon_username varchar2(30);');
	DBMS_OUTPUT.PUT_LINE('VAR mon_con_cnt number;');
    DBMS_OUTPUT.PUT_LINE('VAR mon_report CLOB;');
    DBMS_OUTPUT.PUT_LINE('VAR mon_sql_id VARCHAR2(13);');
    DBMS_OUTPUT.PUT_LINE('SET ECHO OFF FEED OFF VER OFF SHOW OFF HEA OFF LIN 3000 NEWP NONE PAGES 0 LONG 2000000 LONGC 2000 SQLC MIX TAB ON TRIMS ON TI OFF TIMI OFF ARRAY 100 NUMF "" SQLP SQL> SUF sql BLO . RECSEP OFF APPI OFF AUTOT OFF;');
    -- cursor variable to avoid error on 10g since v\$sql_monitor didn't exist then
    OPEN mon_cv FOR
    select * from                                                                               
    ( select sql_id,elapsed_time,sql_exec_start,sql_exec_id, sql_plan_hash_value,status,username,round(avg_elap/1e6) as avg_time,con_cnt 
     from                                                                                       
     (     SELECT sql_id, elapsed_time, sql_exec_start, sql_exec_id, sql_plan_hash_value,            
             row_number() over (partition by sql_id order by elapsed_time desc) as rn, 
             sum(elapsed_time) over (partition by sql_id) as total_elap,
			 avg(elapsed_time) over (partition by sql_id) as avg_elap,
    		 count(*) over (partition by sql_id ) as con_cnt,         
             username,			 
             decode(status,'EXECUTING','EXEC','DONE (ERROR)','ERROR','DONE') as status                                                       
      FROM gv$sql_monitor WHERE process_name = 'ora' and sql_plan_hash_value>0                                         
     ) where rn=1 ORDER BY total_elap desc                                                    
    ) where rownum<=^^sql_monitor_reports.;
    LOOP
      FETCH mon_cv INTO mon_rec;
      EXIT WHEN mon_cv%NOTFOUND;
      --l_count := l_count + 1;
      --IF l_count > ^^sql_monitor_reports. THEN
      --  EXIT; -- exits loop
      --END IF;
      DBMS_OUTPUT.PUT_LINE('EXEC :mon_sql_id := '''||mon_rec.sql_id||''';');
	  DBMS_OUTPUT.PUT_LINE('EXEC :mon_elapsed_time := '||lpad(round(mon_rec.elapsed_time/1e6),5,'0')||';');
	  
      DBMS_OUTPUT.PUT_LINE('EXEC :mon_exec_start := '''||TO_CHAR(mon_rec.sql_exec_start, 'YYYYMMDDHH24MISS')||''';');
      DBMS_OUTPUT.PUT_LINE('EXEC :mon_exec_id := '||TO_CHAR(mon_rec.sql_exec_id)||';');
      DBMS_OUTPUT.PUT_LINE('EXEC :mon_sql_plan_hash_value := '||TO_CHAR(mon_rec.sql_plan_hash_value)||';');
      DBMS_OUTPUT.PUT_LINE('EXEC :mon_status := '''||substr(mon_rec.status,1,4)||''';');
      DBMS_OUTPUT.PUT_LINE('SPO ora_sqlmon_'||lpad(round(mon_rec.elapsed_time/1e6),5,'0')||'_'||TO_CHAR(mon_rec.sql_plan_hash_value)||'_'||mon_rec.sql_id||'_'||TO_CHAR(mon_rec.sql_exec_start, 'YYYYMMDDHH24MISS')||'_'||mon_rec.status||'_'||mon_rec.username||'_'||to_char(mon_rec.avg_time)||'_'||to_char(mon_rec.con_cnt)||'.html;');
      DBMS_OUTPUT.PUT_LINE('SELECT ''<!-- ''||TO_CHAR(SYSDATE, ''YYYY-MM-DD/HH24:MI:SS'')||'' -->'' FROM dual;');
      DBMS_OUTPUT.PUT_LINE('set term on');
      DBMS_OUTPUT.PUT_LINE('PRO <!-- begin DBMS_SQLTUNE.REPORT_SQL_MONITOR: '||mon_rec.sql_id);
      DBMS_OUTPUT.PUT_LINE('set term off');
      DBMS_OUTPUT.PUT_LINE('BEGIN');
      DBMS_OUTPUT.PUT_LINE('  :mon_report := DBMS_SQLTUNE.REPORT_SQL_MONITOR (');
      DBMS_OUTPUT.PUT_LINE('    sql_id         => :mon_sql_id,');
      DBMS_OUTPUT.PUT_LINE('    sql_exec_start => TO_DATE(:mon_exec_start, ''YYYYMMDDHH24MISS''),');
      DBMS_OUTPUT.PUT_LINE('    sql_exec_id    => :mon_exec_id,');
      DBMS_OUTPUT.PUT_LINE('    report_level   => ''ALL'',');
      DBMS_OUTPUT.PUT_LINE('    type           => ''ACTIVE'' );');
      DBMS_OUTPUT.PUT_LINE('END;');
      DBMS_OUTPUT.PUT_LINE('/');
      DBMS_OUTPUT.PUT_LINE('set term on');
      DBMS_OUTPUT.PUT_LINE('PRO end -->');
      DBMS_OUTPUT.PUT_LINE('set term off'); 
      DBMS_OUTPUT.PUT_LINE('SELECT ''<!-- ''||TO_CHAR(SYSDATE, ''YYYY-MM-DD/HH24:MI:SS'')||'' -->'' FROM dual;');
      DBMS_OUTPUT.PUT_LINE('SELECT :mon_report FROM DUAL;');
      DBMS_OUTPUT.PUT_LINE('SELECT ''<!-- ''||TO_CHAR(SYSDATE, ''YYYY-MM-DD/HH24:MI:SS'')||'' -->'' FROM dual;');
      DBMS_OUTPUT.PUT_LINE('SPO OFF;');
    END LOOP;
    CLOSE mon_cv;
END;
/
SPO OFF;
@_sqlmon.sql

1. 脚本全称定位

Oracle 自动化批量生成 SQL Monitor 实时执行监控 HTML 报告 工具脚本,基于 gv$sql_monitor(全局 SQL 监控视图)+ DBMS_SQLTUNE.REPORT_SQL_MONITOR 实现。
  • 输入变量: topn(DEF sql_monitor_reports = '$topn'), 代表抓取 TOP N 慢 SQL;

  • 输出:一批独立 HTML 文件,每个文件对应 一条 正在执行 / 刚执行完的慢 SQL 完整执行剖析报告;

  • 执行逻辑: 先动态生成临时 SQL 脚本 → 再自动调用临时脚本批量导出 HTML → 最后清理临时文件。

2. 核心功能拆解

(1)数据源:GV$SQL_MONITOR

Oracle 实时 SQL 监控视图,只要 SQL 满足任一条件会被自动采集:
  • SQL 并行执行;
  • 单次执行耗时超 5 秒;
  • 手动开启 MONITOR hint;存储每条 SQL 单次执行的完整运行快照:耗时、执行起止时间、执行 ID、执行状态、操作用户、执行计划 HASH、并行度、平均耗时、并发次数等。

(2)筛选逻辑(游标核心 SQL)

select * from (
 select sql_id,elapsed_time,sql_exec_start,sql_exec_id, sql_plan_hash_value,status,username,
 round(avg_elap/1e6) as avg_time,con_cnt 
 from (    
  SELECT 
    sql_id, elapsed_time, sql_exec_start, sql_exec_id, sql_plan_hash_value,
    row_number() over (partition by sql_id order by elapsed_time desc) as rn, 
    sum(elapsed_time) over (partition by sql_id) as total_elap,
    avg(elapsed_time) over (partition by sql_id) as avg_elap,
    count(*) over (partition by sql_id ) as con_cnt,         
    username,			 
    decode(status,'EXECUTING','EXEC','DONE (ERROR)','ERROR','DONE') as status                                                       
  FROM gv$sql_monitor 
  WHERE process_name = 'ora' and sql_plan_hash_value>0                                         
 ) where rn=1 
 ORDER BY total_elap desc         ) where rownum<=^^sql_monitor_reports.


筛选规则:
  • 过滤后台 ora 进程业务 SQL(排除后台系统进程);
  • 过滤无执行计划的记录 sql_plan_hash_value>0;
  • 同一条 sql_id 只保留 单次执行耗时最长的那条记录 row_number() partition by sql_id order by elapsed_time desc rn=1;
  • 按该 SQL 总累计耗时倒序;
  • 只取前 TopN 条(外部传入 topn 变量)。
附加计算字段:
  • avg_time:单次执行平均耗时(秒)

  • con_cnt:该 SQL 一共执行多少次

  • status 格式化:EXECUTING = 正在运行、ERROR = 执行报错、DONE = 正常执行完成

(3)动态拼接生成临时脚本 _sqlmon.sql

PL/SQL 块通过 dbms_output 逐行输出 SQLPlus 命令,写入临时文件:
  • 定义绑定变量存储单条 SQL 监控入参(sql_id、执行时间、exec_id、报告大对象 clob);
  • SPOOL 单独文件,文件名包含关键维度(耗时、计划 HASH、sql_id、执行时间、状态、操作用户、平均耗时、执行次数),便于事后快速识别;
  • 调用 DBMS_SQLTUNE.REPORT_SQL_MONITOR
    •  type=>'ACTIVE':生成交互式动态 HTML(可看执行步骤、等待事件、行扫描、IO、CPU 消耗)
    •  report_level=>'ALL':输出全量信息(等待事件、执行计划、行统计、绑定变量、并行信息)

  • 打印 CLOB 报告到文件,关闭 spool。

(4)执行与清理流程

  1. spool _sqlmon.sql:把 PL/SQL 拼接的所有导出逻辑写入临时文件;
  2. / 执行 PL/SQL 生成完整临时脚本;
  3. spool off 关闭临时文件输出;
  4. @_sqlmon 自动执行批量导出 HTML;
  5. ! rm _sqlmon.sql 操作系统命令删除临时脚本,不留垃圾文件。

(5)SQLPlus 环境参数作用

set linesize 300 pagesize 0
SET DEF ^ TERM OFF ECHO ON AUTOP OFF VER OFF SERVEROUT ON SIZE 1000000;
SET TERM ON ECHO OFF;
set trims on
  • DEF ^ :把变量标识符修改为 ^,避免 & 变量冲突;

  • SERVEROUT ON SIZE 1000000 :放大缓冲区,防止长报告截断;

  • trims on :去除行末尾空格,缩小 HTML 文件体积;

  • 批量生成时关闭终端输出,提升导出速度,不刷屏。

输出 HTML 报告包含什么核心内容(分析价值)

DBMS_SQLTUNE.REPORT_SQL_MONITOR(type=>'ACTIVE') 交互式 HTML 包含:

    1. 真实运行执行计划(实际行数 / 预估行数对比,精准判断索引失效、全表扫描);
    2. 时序耗时分布:CPU 时间、IO 等待、锁等待、网络等待占比;
    3. 逐行执行步骤:扫描行数、返回行数、逻辑读、物理读、临时段消耗;
    4. 等待事件明细:db file sequential read、latch、TX 行锁、enq 等瓶颈;
    5. 并行执行进程分发、PX 等待;
    6. 执行报错堆栈(status=ERROR 时可见);
    7. 内存 / PGA 临时表空间使用情况。

    8. SQL 完整文本、绑定变量值;

脚本使用场景

场景 1:生产业务突发卡顿、响应超时(最常用)

业务高峰期页面超时、交易堆积、数据库 CPU/IO 飙升,需要快速定位 当前正在运行的慢 SQL。AWR 只能看历史快照,延迟高;gv$sql_monitor 是实时视图,本脚本一键导出 TopN 实时 SQL 完整运行剖析报告,不用单条手动生成报告。

场景 2:批量巡检每日高危慢 SQL

纳入定时 shell 巡检脚本,每日业务高峰执行 @sqlmon_script topn=20,自动产出 20 条累计耗时最高 SQL 的 HTML 报告,归档留存,用于性能复盘、慢 SQL 优化台账。

场景 3:批量定位执行报错 SQL

筛选 status=ERROR 的 HTML 文件,快速抓取运行时报 ORA 错误的业务 SQL,定位数据异常、约束报错、逻辑 bug。

场景 4:大事务 / 批量 ETL / 报表 SQL 性能诊断

报表、数据同步 ETL 执行时间几十分钟,单条手动生成报告繁琐;脚本批量导出,对比多条报表 SQL 的 IO、执行计划差异。

场景 5:并发热点 SQL 瓶颈分析

文件名携带 con_cnt(执行次数)、avg_time(平均耗时),快速区分:单次慢但执行少 / 单次一般但并发极高的热点 SQL。

场景 6:变更上线后性能回归验证

版本上线前后分别执行脚本导出 HTML,对比同 SQL_ID 执行计划、物理读、耗时变化,判断索引 / 逻辑变更是否引发性能退化。

场景 7:无法登录应用,只能数据库侧排障

无应用日志、无中间件日志,仅通过数据库侧实时监控文件,还原 SQL 运行时瓶颈。

案例分享:

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

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