v$active_session_history本身不记录递归调用归属关系,需关联v$session(sql_id/prev_sql_id)、v$sql(sql_type/program_id)及V$TOPLEVELCALL视图,结合call_depth和toplevelcall#实现递归开销精准归因。

查 v$active_session_history 时必须关联 v$sql 和 v$session
ASH 本身不记录递归调用的归属关系,sql_id 字段只显示当前采样时刻正在执行的语句 ID——它可能是用户 SQL,也可能是系统生成的递归 SQL(如 select /*+ rule */ from sys.undo$)。单看 v$active_session_history 无法判断某条递归 SQL 是被哪个顶级调用触发的。
真正能建立归属链的是 v$session 的 sql_id(当前执行的用户语句)和 prev_sql_id(上一条执行的语句),再结合 v$sql 中的 sql_type('RECURSIVE SQL' 或 'PL/SQL EXECUTION')和 program_id(指向触发它的存储过程或触发器)。
- 优先过滤
session_state = 'ON CPU'且event IS NULL的样本:这类样本大概率在执行递归逻辑,而非等待 - 别只查
sql_id不为空的行;很多递归调用(如空间分配、字典查询)的sql_id是0000000000000000,得靠sql_opname IN ('ALLOCATE', 'SPACE MANAGEMENT')辅助识别 -
v$session.prev_sql_id在高并发下可能被覆盖,不能作为唯一依据;需交叉比对sample_time时间窗口内该会话的连续采样序列
用 V$TOPLEVELCALL 直接定位递归开销归属
V$TOPLEVELCALL 是 Oracle 专为解决这个问题设计的视图:它把递归调用消耗的 CPU、等待时间、物理读等资源,全部归到发起它的顶级调用(即客户端发来的原始 SQL 或 PL/SQL 块)名下。它不依赖 sql_id 关联,而是通过内部调用栈跟踪实现归属。
- 关键字段是
toplevelcall#(顶级调用唯一标识)和recursive_call#(对应递归调用编号),二者通过call_depth构成树状结构 - 查耗时最长的递归路径:按
cpu_time + wait_time排序,重点关注call_type = 'RECURSIVE SQL'且call_depth > 1的记录 - 注意
sql_id为空但object_name非空的情况:比如object_name = 'SYS.UNDO$',说明是 undo 管理相关的递归动作,根源很可能在某个长事务的 DML 上
区分硬解析引发的递归 vs. DDL/空间管理类递归
两类递归的诊断路径完全不同。硬解析触发的递归集中在数据字典访问(dc_objects, dc_users),而 DDL 或大 DML 触发的递归集中在空间分配、索引维护、undo header 更新等后台操作。
- 若高频出现
event = 'row cache lock'+sql_id = 0000000000000000+p1 = 8(对应dc_objects),基本可断定是硬解析风暴,根源在应用端未绑定变量或频繁执行 DDL - 若大量
session_state = 'ON CPU'样本中program = 'oracle@host (DBW0)'或module = 'Space Management',说明是空间扩展类递归,应检查表/索引的next_extent设置、是否启用autoallocate、以及是否存在异常大的单次插入 -
V$TOPLEVELCALL中call_name含'kcbgcur'或'ktspgfblk'是典型的空间管理递归函数名,不用反查 SQL 文本就能快速归类
容易忽略的递归调用陷阱
最常被漏掉的不是“找不到递归 SQL”,而是误判了谁在承担成本。比如一个简单的 INSERT 执行慢,v$sql 显示其自身 elapsed_time 很低,但实际耗时全在递归调用里——而这些递归调用的 sql_id 可能属于系统后台进程,不会出现在应用会话的 v$session 中。
- 不要只盯着
v$active_session_history.session_id;得同步查inst_id和blocking_inst_id,RAC 环境下递归调用可能跨实例执行 -
V$TOPLEVELCALL默认只保留最近几分钟活跃调用,若问题已过去,需用DBA_HIST_TOPLEVELCALL(需开启相应统计采集) - PL/SQL 匿名块中的 SQL 执行,顶级调用
toplevelcall#可能和包名一致,但sql_id为空——这时要靠plsql_entry_object_id关联dba_objects定位具体代码位置


















