ITPub博客

首页 > 数据库 > Oracle > 第81期 批量抓取数据库中耗时最高的SQL自动生成【Active HTML格式的SQL Monitor报告】

第81期 批量抓取数据库中耗时最高的SQL自动生成【Active HTML格式的SQL Monitor报告】

原创 Oracle 作者:pxbibm 时间:2026-09-01 17:42:45 0 删除 编辑
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 = '5';
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

Oracle SQL Monitor 批量导出 HTML 报告脚本完整解析

整体概述

这是一段 SQL*Plus 脚本,不是直接业务 SQL;属于 Oracle DBA 性能诊断工具脚本。

核心作用:批量抓取当前数据库中耗时最高的 SQL 监控信息,自动生成【Active HTML 格式的 SQL Monitor 报告】。

DBMS_SQLTUNE.REPORT_SQL_MONITOR 是 Oracle 真实执行监控报告,比 AWR、explain plan 更真实,包含运行时统计:行源耗时、IO、CPU、等待事件、并行、绑定变量等。 数据源: gv$sql_monitor,该视图记录 被 SQL Monitor 监控过的 SQL 执行记录(Oracle 11g + 特性)。

工作模式:二阶生成脚本

  1. 第一阶段:PL/SQL 块查询 gv$sql_monitor,动态生成中间脚本文件 _sqlmon.sql
  2. 第二阶段: @_sqlmon.sql 执行生成出来的脚本,输出一个个 .html 报告文件,打开浏览器可直接查看可视化 SQL Monitor。

逐段拆解

第一部分:SQL*Plus 环境参数设置

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
  1. linesize 300:输出行宽; pagesize 0:关闭分页页眉页脚

  2. SET DEF ^: 修改替代变量符号,默认是 &改成 ^,避免脚本内部 &触发变量交互,防止生成脚本出错。

  3. TERM OFF:关闭屏幕输出(屏幕不刷大量日志,只写文件); TERM ON打开屏幕输出

  4. ECHO ON/OFF:是否回显执行的 SQL 文本

  5. AUTOP OFF:关闭 autotrace; VER OFF关闭变量替换回显

  6. SERVEROUT ON SIZE 1000000:打开 dbms_output,缓冲区开大,PL/SQL 块可以输出大量文本,用来生成脚本内容。

  7. TRIMS ON:去掉行尾空格。

DEF sql_monitor_reports = '5';

定义替代变量:控制 最多生成多少条 top SQL 的监控报告,默认 5 条,可以调用时传入: def sql_monitor_reports=10; 导出 10 个报告。

set term off
SPO _sqlmon.sql;

SPOOL 开始写文件 _sqlmon.sql;后面 PL/SQL 里面所有 DBMS_OUTPUT.PUT_LINE()输出的文本全部写入这个文件。 这是采用编程思想:PL/SQL 查询数据,动态拼接 SQL*Plus 脚本代码,写入文件,后续再执行这个文件。

PL/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
    -- 定义SQL*Plus绑定变量,后续生成脚本中使用
    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);');

VAR xxx 是 SQL*Plus 绑定变量, mon_report CLOB 用来接收 DBMS_SQLTUNE.REPORT_SQL_MONITOR 返回的大 HTML 文本。

核心游标查询 gv$sql_monitor

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.;

查询逻辑拆解数据源: gv$sql_monitor RAC 环境使用全局视图,单实例也可以运行。 过滤条件:

  • process_name='ora':只看数据库后台进程执行 SQL,排除并行从属之外部分后台;
  • sql_plan_hash_value>0:过滤没有执行计划的记录。

窗口函数逻辑:

  1. partition by sql_id:按 sql_id 分组

  2. row_number() over(partition by sql_id order by elapsed_time desc) rn=1: 同一个 sql_id 取耗时最大的那一次执行记录,避免同一个 SQL 输出多份报告。

  3. sum(elapsed_time) over(partition by sql_id) total_elap:该 sql_id 累计总耗时(单位微秒)

  4. avg(elapsed_time) over():单次执行平均耗时; count(*) over():该 sql_id 一共被监控执行多少次。

  5. 外层按 total_elap desc 排序,取前 N(sql_monitor_reports)条。

elapsed_time 单位: 微秒 μs,除以 1e6 得到秒。

LOOP 循环动态生成 spool 报告代码

LOOP
  FETCH mon_cv INTO mon_rec;
  EXIT WHEN mon_cv%NOTFOUND;
  -- 输出大量 EXEC :xxx := 值;给SQL*Plus绑定变量赋值
  DBMS_OUTPUT.PUT_LINE('EXEC :mon_sql_id := '''||mon_rec.sql_id||''';');
  ......
  -- SPOOL 文件名:把关键信息编码进文件名,方便事后识别
  DBMS_OUTPUT.PUT_LINE('SPOOL 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;');

生成的文件名示例: ora_sqlmon_00243_234234234_8kdfjdfj234_20260831102000_EXEC_SYS_120_3.html

文件名包含:单次执行耗时 (秒)、plan_hash_value、sql_id、执行开始时间、状态、用户名、平均耗时、执行次数。不用打开 html,文件名就能掌握基本信息。

-- 调用 REPORT_SQL_MONITOR
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'' );');
  • type=>'ACTIVE':生成 交互式动态 HTML,可以点开折叠执行计划,查看等待事件,不是静态文本。

  • report_level=>'ALL':输出全部细节:计划、统计、绑定变量、等待事件、并行信息。

sql_exec_start + sql_exec_id 两个字段组合,才能唯一确定某一次 SQL 执行;只传 sql_id 会返回汇总报告。

DBMS_OUTPUT.PUT_LINE('SELECT :mon_report FROM DUAL;'); --输出CLOB html内容到spool文件
DBMS_OUTPUT.PUT_LINE('SPOOL OFF;');

SELECT :mon_report FROM DUAL; 将 CLOB 报告内容输出,spool 写入 html 文件。

循环结束关闭游标;PL/SQL 块结束; SPOOL OFF; 关闭输出文件 _sqlmon.sql。

@_sqlmon.sql

执行刚刚动态生成的脚本文件,真正完成每一条 SQL Monitor html 落地。


用途、适用场景、限制、坑点

用途

  1. 故障现场快速采集:数据库出现性能抖动、慢 SQL、高 CPU、高 IO,一键导出 Top-N 真实 SQL Monitor 报告。

  2. 事后分析:把 html 下载本地浏览器打开,不需要登录数据库,团队 DBA 可以共享分析。

  3. 对比:同一个 sql_id 不同 plan_hash_value 的执行报告,看执行计划变化带来性能差异。

  4. 问题 SQL:正在运行 (EXECUTING)、执行报错 (ERROR)、执行完成 (DONE) 都可以抓取。

适用场景

  1. Oracle 11gR2 /12c /18c /19c /21c;RAC 环境同样支持(使用 gv$sql_monitor)

  2. 性能应急排查:业务卡顿,来不及一个个手动调用 DBMS_SQLTUNE.REPORT_SQL_MONITOR,批量导出。

  3. 灾难演练、压力测试:压测结束运行脚本收集所有 top 监控报告留存。

  4. 巡检脚本集成,定期抓取 top SQL Monitor 归档。

重要前提限制(高频踩坑)

  1. SQL Monitor 不是所有 SQL 都会记录!Oracle 只有满足条件才会记入 v$sql_monitor:
  • SQL 并行执行;
  • 单次执行消耗超过 5 秒;
  • 手动开启监控 /*+ MONITOR */ hint;

短快 SQL 不会出现在 gv$sql_monitor,脚本抓不到。

  2.gv$sql_monitor 内存视图,实例重启清空;信息不会持久化,AWR 不保存 SQL Monitor 完整报告。故障过后再运行脚本,历史记录已经消失。 必须故障现场执行。

  3.权限要求:执行脚本用户需要

grant select_catalog_role to xxx;
grant execute on dbms_sqltune to xxx;

缺少 dbms_sqltune执行权限脚本报错。

     4. 脚本运行目录:spool 输出文件生成在 SQL*Plus 当前工作目录;linux sqlplus 登录时所在操作系统目录。
     5. 变量 ^^sql_monitor_reports.,使用自定义替代符号 ^,不要修改 set def 值。
     6. 10g 没有 v$sql_monitor视图,脚本不支持,脚本内使用 ref cursor 做了兼容避免直接编译报错,但 10g 运行无数据。

脚本优势对比手动方式

手动单条获取 SQL Monitor:

SET LONG 1000000
SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(sql_id=>'xxxx',sql_exec_start=>xxx,sql_exec_id=>xxx,type=>'ACTIVE',report_level=>'ALL') FROM dual;

手动要填一堆参数,一条条 spool;脚本自动筛选 top 耗时、自动拼接文件名,批量导出。

生产使用示例

-- sqlplus / as sysdba @脚本名.sql
-- 修改导出报告数量,在脚本运行前定义
DEF sql_monitor_reports = '10';
@sqlmon_collect.sql

运行完成当前目录生成一批 ora_sqlmon_*.html。

Oracle 生产加固版批量生成 SQL‑Monitor Active HTML 报告脚本

  1. 增加权限前置校验,检测 DBMS_SQLTUNE执行权限、视图访问权限
  2. 过滤 SYS/SYSTEM 系统用户 SQL,只抓业务用户;过滤内部递归 SQL
  3. 增加输出目录提示,自动清理临时文件 _sqlmon.sql
  4. 文件名缩短,规避 OS 文件名过长限制;文件名增加时间戳前缀
  5. 异常捕获,PL/SQL 块增加 exception;RAC 友好, gv$sql_monitor
  6. 可传入参数自定义导出报告数量;默认取 Top8
  7. 增加执行日志输出,记录抓取到多少条监控记录;过滤无执行计划记录
  8. 修复原版注释掉的计数逻辑;增加对 sql_exec_start is null脏数据过滤
  9. SPOOL 文件增加 noisy 输出屏蔽;增加字符集提示,避免 HTML 乱码
  10. 增加使用说明、权限脚本、清理脚本

适用版本:Oracle 11gR2 ~ 19c/21c; 必须故障现场执行, gv$sql_monitor是内存视图,重启实例丢失。 保存文件: gen_sqlmon_html.sql


REM ==============================================================
REM  gen_sqlmon_html.sql
REM  生产加固版批量导出 SQL Monitor ACTIVE HTML报告
REM  输入参数: DEF sql_monitor_reports = 8; -- 需要导出top N
REM  执行: sqlplus / as sysdba @gen_sqlmon_html.sql
REM  输出: sqlmon_*.html, 临时文件 _sqlmon.sql (脚本自动删除)
REM ==============================================================
SET DEF ^ TERM OFF ECHO OFF AUTOP OFF VER OFF SERVEROUT ON SIZE 2000000;
SET TERM ON;
SET LINESIZE 300 PAGESIZE 0 TRIMS ON FEEDBACK OFF;
PRO
PRO ########################################################
PRO ##  Oracle SQL‑Monitor HTML批量采集工具(生产加固版)
PRO ########################################################
PRO
REM 定义导出报告数量,可外部传入,默认8
DEF sql_monitor_reports = '8';
REM 权限校验
DECLARE
  v_cnt NUMBER;
BEGIN
  SELECT COUNT(*) INTO v_cnt FROM all_objects WHERE object_name='DBMS_SQLTUNE' AND owner='SYS';
  IF v_cnt=0 THEN
    DBMS_OUTPUT.PUT_LINE('!!! ERROR: DBMS_SQLTUNE 对象不存在,版本不支持!');
  END IF;
EXCEPTION WHEN OTHERS THEN
  DBMS_OUTPUT.PUT_LINE('!!! ERROR 权限异常:'||SQLERRM);
END;
/
REM 开始生成中间脚本
SET TERM OFF;
SPOOL _sqlmon.sql;
DECLARE
  l_count        NUMBER :=0;
  l_max_reports  NUMBER := TO_NUMBER('^^sql_monitor_reports.');
  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
  -- 定义SQL*Plus绑定变量
  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 ');
  DBMS_OUTPUT.PUT_LINE('LONG 2000000 LONGC 2000 SQLC MIX TAB ON TRIMS ON TI OFF TIMI OFF ARRAY 100 ');
  DBMS_OUTPUT.PUT_LINE('NUMF "" SQLP SQL> SUF sql BLO . RECSEP OFF APPI OFF AUTOT OFF;');
  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) 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) rn,
          SUM(elapsed_time) OVER(PARTITION BY sql_id) total_elap,
          AVG(elapsed_time) OVER(PARTITION BY sql_id) avg_elap,
          COUNT(*) OVER(PARTITION BY sql_id) con_cnt,
          username,
          DECODE(status,'EXECUTING','EXEC','DONE (ERROR)','ERROR','DONE') status
        FROM gv$sql_monitor
        WHERE process_name = 'ora'
          AND sql_plan_hash_value > 0
          AND sql_exec_start IS NOT NULL
          AND username NOT IN ('SYS','SYSTEM') -- 过滤系统用户
      ) WHERE rn = 1
      ORDER BY total_elap DESC
    ) WHERE ROWNUM <= l_max_reports;
  LOOP
    FETCH mon_cv INTO mon_rec;
    EXIT WHEN mon_cv%NOTFOUND;
    l_count := l_count +1;
    DBMS_OUTPUT.PUT_LINE('EXEC :mon_sql_id := '''||mon_rec.sql_id||''';');
    DBMS_OUTPUT.PUT_LINE('EXEC :mon_elapsed_time := '||ROUND(mon_rec.elapsed_time/1e6)||';');
    DBMS_OUTPUT.PUT_LINE('EXEC :mon_exec_start := '''||TO_CHAR(mon_rec.sql_exec_start,'YYYYMMDDHH24MISS')||''';');
    DBMS_OUTPUT.PUT_LINE('EXEC :mon_exec_id := '||mon_rec.sql_exec_id||';');
    DBMS_OUTPUT.PUT_LINE('EXEC :mon_sql_plan_hash_value := '||mon_rec.sql_plan_hash_value||';');
    DBMS_OUTPUT.PUT_LINE('EXEC :mon_status := '''||SUBSTR(mon_rec.status,1,4)||''';');
    -- 缩短文件名:时间戳_耗时_sqlid_planhash.html,规避文件名过长
    DECLARE
      v_filename VARCHAR2(255);
    BEGIN
      v_filename := 'sqlmon_'
        ||TO_CHAR(SYSDATE,'YYYYMMDDHH24MISS')||'_'
        ||ROUND(mon_rec.elapsed_time/1e6)||'s_'
        ||mon_rec.sql_id||'_'
        ||mon_rec.sql_plan_hash_value||'.html';
      DBMS_OUTPUT.PUT_LINE('SPOOL '||v_filename||';');
    END;
    DBMS_OUTPUT.PUT_LINE('SELECT ''<!-- GENERATED:'||TO_CHAR(SYSDATE,''YYYY‑MM‑DD HH24:MI:SS'')||' sql_id:''||:mon_sql_id||'' -->'' FROM dual;');
    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('SELECT :mon_report FROM DUAL;');
    DBMS_OUTPUT.PUT_LINE('SPOOL OFF;');
  END LOOP;
  CLOSE mon_cv;
  DBMS_OUTPUT.PUT_LINE('PRO -- Total generated sqlmon report count: '|| l_count);
EXCEPTION WHEN OTHERS THEN
  DBMS_OUTPUT.PUT_LINE('PRO !!! PL/SQL ERROR: '||SQLERRM);
  IF mon_cv%ISOPEN THEN CLOSE mon_cv; END IF;
END;
/
SPOOL OFF;
SET TERM ON;
PRO
PRO 执行动态生成脚本 _sqlmon.sql ...
PRO
@_sqlmon.sql
REM 清理临时脚本文件
HOST rm -f _sqlmon.sql;
PRO
PRO =====================================================
PRO 采集完成!
PRO 输出文件:当前操作系统目录下 sqlmon_*.html
PRO 提示:sqlplus spool文件输出路径 = sqlplus登录时OS工作目录
PRO =====================================================
SET DEF &;

生成的示例文件 gen_sqlmon_html.zip


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

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