查v$active_session_history须用动态时间窗口(如sample_time > sysdate - interval '10' minute),rac环境用gv$,按sql_id+plan_hash_value聚合,结合session_state/event识别真慢与假性耗时,再通过dbms_xplan验证执行计划及绑定变量影响。

查 v$active_session_history 必须用动态时间窗口
ASH 内存只保留约 1 小时,高负载下可能只剩 20–30 分钟;写死时间字符串(如 '2026-09-17 14:00:00')极易因 NLS 设置、时区偏差或采样非连续性漏掉关键样本。
正确做法是用 SAMPLE_TIME > SYSDATE - INTERVAL '10' MINUTE 这类动态表达式——它不受会话时区影响,且能精确覆盖问题发生的真实窗口。
- 别用
BETWEEN:比如卡顿发生在14:02:59.8,而你查14:02:00到14:03:00,但采样只在.0/.1/.2秒发生,就可能错过 - RAC 环境必须用
gv$active_session_history,否则只看到当前节点样本,若问题在 node2,你在 node1 上查不到 - 时间范围建议从窄到宽:先试
- INTERVAL '5' MINUTE,没结果再扩到15或30分钟
按 SQL_ID + PLAN_HASH_VALUE 聚合才准
同一 SQL_ID 可能对应多个子游标(child_number 不同),执行计划差异极大;只按 SQL_ID 统计,会把全表扫描和索引扫描混在一起,掩盖真实瓶颈。
例如一条 SQL 在某次执行中因绑定变量值触发数据倾斜,走了哈希连接+溢出写临时表,但平均耗时被其他快执行拉低,单看 SQL_ID 的总样本数根本看不出异常。
- 必须
GROUP BY SQL_ID, PLAN_HASH_VALUE,不是只按SQL_ID - 统计用
COUNT(*)(采样次数)比SUM(DELTA_TIME)更稳定,后者在短事件中常为 0 或被合并 - 加
HAVING COUNT(*) > 10过滤噪声,避免把偶发的单次采样当问题
区分“真慢”和“假性耗时长”
ASH 中某条 SQL_ID 出现大量间隔长的样本,不等于它单次执行花了几十秒——更可能是被阻塞、硬解析卡住,或 PL/SQL 循环反复调用同一条语句。
关键看 session_state 和 event 组合:
-
session_state = 'ON CPU'且event为空 → 真正在执行 SQL,重点查执行计划 -
session_state = 'WAITING'且event = 'enq: TX - row lock contention'→ 卡在锁上,不是 SQL 本身慢 -
sql_id IS NULL但plsql_entry_object_id有值 → 卡在 PL/SQL 层(如 JSON 解析、正则匹配),没发 SQL
再交叉验证 v$sql:elapsed_time / executions 若只有几毫秒,但 ASH 里一堆 30s+ 样本,基本可排除真实执行慢,转向查阻塞链或硬解析。
查到 SQL_ID 后必须立刻看执行计划
拿到可疑 SQL_ID 和 PLAN_HASH_VALUE 后,别急着改 SQL 文本。先确认它是否真在走劣质路径:
- 用
DBMS_XPLAN.DISPLAY_CURSOR('xxx', null, 'outline +peeked_binds')查当前内存中计划,重点看:是否走了全表扫描、连接方式是否合理、分区裁剪是否生效 - 用
DBMS_XPLAN.DISPLAY_AWR('xxx')对比历史计划,确认plan_hash_value是否变化;若变了,再查dba_tab_statistics看统计信息是否过期 - 若计划没变、统计信息也新,但某次执行突然巨慢,立刻检查绑定变量值:
WHERE status = :b1,:b1 = 'Z'返回 500 万行,而优化器按平均值估算只选了 nested loop
最易忽略的一点:top_level_sql_id 字段。它指向最外层 SQL(比如调用该 SQL 的匿名块里的 EXEC),能帮你回溯真实业务入口,而不是陷在中间层视图或包过程里打转。











