直接查v$active_session_history中session_state='ON CPU'样本数最多的sql_id即可定位高CPU SQL;1样本≈10ms,数值越大说明CPU占用越严重,需结合sql_plan_hash_value分析执行计划变化,并注意ASH内存数据仅保留约1小时。
直接查 v$active_session_history 里 session_state = 'on cpu' 样本数最多的 sql_id,就是最可能拖垮 cpu 的那条 sql ——1 个样本 ≈ 10ms,500 个样本就代表它独占了约 5 秒 cpu 时间。
为什么不能只看执行次数或执行时间?
ASH 不记录 SQL 执行了多少次、耗时多久,它只记录“某一秒,这个会话正在干啥”。所以高 CPU 消耗的 SQL,不是跑得慢,而是“在 CPU 上待得久”。比如一个 sql_id 在 1 小时内被采样到 1200 次且全是 ON CPU,说明它几乎全程霸占 CPU;而另一个执行了 10 分钟但只被采样到 10 次(因为中间大量等待),实际 CPU 占用反而低。
-
session_state = 'ON CPU'是唯一可靠标识 CPU 消耗的字段,wait_class里没有 “CPU” 类别 - 别信
v$sql.elapsed_time或executions,它们反映的是历史累计值,对瞬时 CPU 尖刺毫无意义 - 同一
sql_id可能对应多个子游标(sql_child_number),不同子游标执行计划差异极大,必须连带查sql_plan_hash_value
怎么写查询语句才不漏掉真凶?
以下语句从内存中实时抓取最近 5 分钟的 CPU 样本分布,按 sql_id 聚合统计,并限制返回前 5 名:
SELECT sql_id, COUNT(*) cpu_samples, MAX(sql_plan_hash_value) plan_hash FROM v$active_session_history WHERE session_state = 'ON CPU' AND sample_time > SYSDATE - INTERVAL '5' MINUTE GROUP BY sql_id ORDER BY cpu_samples DESC FETCH FIRST 5 ROWS ONLY;
- 必须加
session_state = 'ON CPU',漏掉这行就会混入大量等待事件数据 - 时间范围建议从 5 分钟起查,太短(如 1 分钟)容易因采样抖动误判;太长(如 1 小时)可能被低频但高烈度的尖刺淹没
- 如果返回空,不是没高 CPU SQL,而是问题不在 SQL 层——可能是硬解析风暴、PL/SQL 循环、递归调用,得切到
event维度再筛
查到 sql_id 后,下一步必须做三件事
光知道 ID 不够,Oracle 里同 ID 不同行为是常态。必须立刻验证执行计划、文本和会话上下文:
- 查 SQL 文本:
SELECT sql_text FROM v$sql WHERE sql_id = '&sql_id' AND ROWNUM <= 3(加ROWNUM防卡死) - 查当前执行计划:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', NULL, 'ALLSTATS LAST')) - 查是否多子游标:
SELECT sql_id, child_number, plan_hash_value, executions, is_bind_sensitive, is_shareable FROM v$sql WHERE sql_id = '&sql_id',重点关注is_bind_sensitive = 'Y'的子游标
特别注意:如果 v$sql 查不到文本,说明该 SQL 已老化出共享池,此时只能靠 dba_hist_sqltext 回溯,但前提是 AWR 快照捕获过它——否则你看到的只是“幽灵 sql_id”。
容易被忽略的两个关键点
一是 v$active_session_history 默认只保留约 1 小时内存数据,超时即丢弃。刚发现 CPU 100%,却查不到任何高样本 sql_id,大概率是 ASH 缓冲区已被覆盖,尤其在低负载实例上,缓冲区几分钟就刷空了。
二是 pga_allocated 和 temp_space_allocated 这类字段在 v$active_session_history 中存在但不稳定,某些版本或补丁下为空,查 PGA/临时段消耗必须优先用 dba_hist_active_sess_history 并指定明确时间戳。


















