v$active_session_history是oracle负载突变时最灵敏的切入点,每秒采样前台会话,通过session_state、event、sql_id快速定位cpu、i/o或锁争用根因,但数据仅内存保留约1小时。
直接查 v$active_session_history 或 gv$active_session_history,用 sample_time 截断到小时级并 count(*) 分组,就能看出哪条 sql_id 在哪一小时内被采样次数突增——这不是“频率”,是“活跃度快照密度”,但对定位突发调用量最有效。
为什么 COUNT(*) 能反映 SQL 执行频次突增
ASH 每秒最多采一个样本(实际受 _ash_sampling_interval 影响),每个样本只记录当时处于 ON CPU 或等待状态的会话。如果某条 SQL 在 14:00–15:00 内被采到 1200 次,而前一小时只有 80 次,大概率不是它变慢了,而是被反复执行了更多轮次(比如应用重试、定时任务误配、缓存击穿后批量回源)。
- 注意:单次长 SQL(如跑 30 秒)可能在该小时内贡献 30 个样本,但它只执行了一次;而 100 次 200ms 的短 SQL 可能只贡献 200 个样本(因采样非连续),所以不能直接换算成“执行次数”,但同比增幅仍具强指示性
-
COUNT(*)突增 +session_state = 'ON CPU'占比高 → 倾向于 CPU 密集型高频调用 -
COUNT(*)突增 +event大量为db file sequential read→ 倾向于 I/O 密集型高频调用(如小查询无缓存、索引失效)
RAC 环境必须用 gv$active_session_history + trunc(SAMPLE_TIME, 'hh24')
用 v$active_session_history 只能看到当前连接实例的数据,漏掉其他节点上的调用峰值。RAC 下必须统一走 gv$active_session_history,且时间截断要一致:
- 错误写法:
to_char(SAMPLE_TIME, 'yyyy-mm-dd hh24')—— 时区不一致时,不同节点返回的字符串可能错位(如 node1 是 +8,node2 是 +0) - 正确写法:
trunc(SAMPLE_TIME, 'hh24')—— 基于数据库服务器本地时区做截断,所有节点计算逻辑一致 - 务必加
WHERE SAMPLE_TIME > SYSDATE - INTERVAL '24' HOUR,避免查全表扫描历史分区(ASH 默认保留 1 小时,但部分环境可能延长)
如何排除“假性激增”:过滤掉解析/递归调用干扰
有些“高频”不是业务 SQL,而是 Oracle 自身动作:比如大量硬解析触发的 sql_id = '0000000000000000',或 PL/SQL 内部递归调用(PL/SQL EXECUTION event)、视图展开产生的子游标。这些会污染统计结果。
- 加过滤条件:
sql_id != '0000000000000000' AND program NOT LIKE '%(P%)'(排除后台进程) - 排除已知低价值 SQL:
sql_id NOT IN (SELECT sql_id FROM dba_hist_sqlstat WHERE executions_delta (需提前建临时表或用 WITH) - 若发现某
SQL_ID在激增时段内plan_hash_value频繁变化,大概率是绑定变量窥探 + 统计信息过期导致计划抖动,而非真实调用量涨——这时应查dba_hist_sql_plan对比 hash 值分布
小时级突增后下一步该查什么
拿到突增的 SQL_ID 和小时段后,别急着看执行计划。先确认它是否“本不该高频”:
- 查
dba_hist_sqlstat中该 SQL 过去 7 天的executions_delta平均值,对比突增小时的估算执行次数(COUNT(*) * 10是粗略上限) - 查
v$sql中last_active_time和first_load_time,确认是不是刚加载的新 SQL(比如发布后首次触发) - 查应用日志对应时段是否有重试标记、定时任务日志是否多跑了一次、API 网关是否上报了异常响应码(429/503)
- 真正容易被忽略的是:某些 SQL 在激增时段内
child_number猛增(如从 1 到 12),说明绑定变量值差异大 + 游标未共享,这比单纯执行次数涨更危险——它会撑爆 shared pool,引发连锁解析压力











