AWR中“SQL ordered by Elapsed Time”的Elapsed Time是总耗时(单次平均×执行次数),非单次耗时;直接除以Executions易得错误结果,须用DBA_HIST_SQLSTAT结合NULLIF、快照边界及PLAN_HASH_VALUE校准后分析单次成本。

AWR里“Elapsed Time”不是单次耗时,别直接除以Executions
AWR报告中“SQL ordered by Elapsed Time”列出的是该SQL在快照区间内所有执行的总耗时(elapsed_time_delta),单位是微秒。它不等于「单次执行时间」,更不是elapsed_time_delta / executions_delta的简单结果——因为executions_delta可能为0(比如只硬解析没执行)、或被截断、或跨快照统计失真。直接这么算,常得出负数或超100秒的假值。
真正可用的单次成本指标,必须从DBA_HIST_SQLSTAT中提取带快照边界的聚合值,并做分母保护:
SELECT sql_id, ROUND(elapsed_time_delta / NULLIF(executions_delta, 0) / 1000000, 3) AS avg_sec, executions_delta AS execs, elapsed_time_delta / 1000000 AS total_sec FROM dba_hist_sqlstat WHERE snap_id BETWEEN 12345 AND 12346 AND executions_delta > 0 AND elapsed_time_delta > 0 ORDER BY avg_sec DESC FETCH FIRST 10 ROWS ONLY;
-
NULLIF(executions_delta, 0)防止除零崩溃 - 限定
snap_id BETWEEN而非模糊时间,避开AWR按小时聚合导致的“平均假象” - 加
executions_delta > 0 AND elapsed_time_delta > 0过滤掉仅解析未执行或统计异常的记录
用DBA_HIST_SQLSTAT查分钟级波动,比AWR报告更准
AWR报告默认每小时一个快照,但业务高峰可能只持续10分钟。如果问题SQL在9:05–9:15间密集执行,而快照是9:00和10:00,它的高成本会被摊平到整小时里,看不出来。
此时应直接查DBA_HIST_SQLSTAT,它底层按快照存储,只要快照存在,就能还原出该SQL在任意两个相邻快照间的瞬时表现:
SELECT s.snap_id, TO_CHAR(sn.begin_interval_time, 'hh24:mi') AS time, s.executions_delta, ROUND(s.elapsed_time_delta / NULLIF(s.executions_delta, 0) / 1000000, 3) AS avg_sec FROM dba_hist_sqlstat s JOIN dba_hist_snapshot sn ON s.snap_id = sn.snap_id WHERE s.sql_id = 'abc123xyz' AND sn.begin_interval_time >= SYSDATE - 1/24 ORDER BY s.snap_id;
- 结果能清晰看到某SQL在9:00–9:01快照间avg_sec=0.8,到9:05–9:06飙升至4.2——这才是真实拐点
- 注意
DBA_HIST_SQLSTAT不存SQL文本,需关联DBA_HIST_SQLTEXT查语句:SELECT sql_text FROM dba_hist_sqltext WHERE sql_id = 'abc123xyz' AND piece = 0 - 若
piece > 0,说明SQL超长,需拼接所有piece序号
对比不同快照区间的单次成本,要固定PLAN_HASH_VALUE
同个sql_id可能对应多个执行计划(plan_hash_value不同),比如因绑定变量窥探、统计信息更新或自适应计划切换。若直接比两个快照区间的avg_sec,可能是在比“全表扫描 vs 索引范围扫描”,结论完全失真。
务必先确认是否同一执行路径:
SELECT plan_hash_value, COUNT(*) cnt, MIN(elapsed_time_delta / NULLIF(executions_delta, 0) / 1000000) min_avg, MAX(elapsed_time_delta / NULLIF(executions_delta, 0) / 1000000) max_avg FROM dba_hist_sqlstat WHERE sql_id = 'abc123xyz' AND snap_id BETWEEN 12345 AND 12346 AND executions_delta > 0 GROUP BY plan_hash_value ORDER BY cnt DESC;
- 若返回多行,说明该SQL在区间内已切换过计划,必须拆开比——比如
plan_hash_value = 123456789时avg_sec均值是0.6,而987654321时是3.1 - 查具体哪次执行用了哪个计划,得看
DBA_HIST_SQL_PLAN:SELECT * FROM dba_hist_sql_plan WHERE sql_id = 'abc123xyz' AND plan_hash_value = 123456789 AND snap_id = 12345 - 生产环境常见陷阱:统计信息自动收集后,
plan_hash_value突变,但DBA没收到告警,单次成本就悄然翻倍
单次成本突增?优先查等待事件,不是改SQL
如果发现某SQL的avg_sec从0.2跳到2.5,第一反应不该是重写SQL或加索引,而是查它卡在哪。AWR里最直接的线索在DBA_HIST_ACTIVE_SESS_HISTORY(ASH):
SELECT
event,
COUNT(*) cnt,
ROUND(AVG(time_waited), 2) avg_wait_ms
FROM dba_hist_active_sess_history
WHERE sql_id = 'abc123xyz'
AND sample_time BETWEEN
(SELECT MIN(begin_interval_time) FROM dba_hist_snapshot WHERE snap_id = 12345)
AND
(SELECT MAX(end_interval_time) FROM dba_hist_snapshot WHERE snap_id = 12346)
GROUP BY event
ORDER BY cnt DESC;
- 若
event = 'enq: TX - row lock contention'占比高,说明是事务锁冲突,不是SQL问题 - 若
event = 'log file sync',大概率是应用提交太频繁,需检查应用层批量提交逻辑 - 若
event = 'ON CPU'且p1text = 'server#',结合v$px_session确认是否并行度失控——这是12c+最易被忽略的CPU型“伪慢SQL”
真正难处理的,是那些avg_sec稳定在1–3秒、执行频次不高、但每天固定时段准时出现的SQL——它们往往藏在定时任务或中间件心跳里,不跑满一小时根本进不了AWR主报告,必须靠DBA_HIST_SQLSTAT窄窗口+plan_hash_value锚定+ASH事件反推,三者缺一不可。











