在进行Oracle数据库性能分析时,有一个关键要点必须牢记:查询时必须加上 session_state = 'ON CPU',因为 MODULE 字段仅标识应用模块,并不能区分当前会话是处于执行状态还是空闲等待。如果遗漏这一条件,统计结果会混入大量等待、空闲等无效样本,容易将“慢模块”误判为“CPU消耗较高的模块”——这是不少初学Oracle性能优化的开发者常遇到的问题。
直接查询 v$active_session_history,利用 module 匹配并结合 session_state = 'on cpu',即可准确定位模块级别的CPU资源消耗。但需要特别留意:module 的值可能被应用程序覆盖,且大小写敏感;此外,历史数据的分析必须通过 dba_hist_active_sess_history 视图完成,不能仅依赖内存中的实时视图。

为什么基于MODULE过滤时必须加上session_state = 'ON CPU'
MODULE 字段本质上是由应用层设置的标记(例如在Spring Boot中通过 setModule 方法赋值),它本身并不反映会话当前到底在做什么操作。同一个模块下,会话很可能有大量时间耗费在I/O等待或锁竞争上,真正在CPU上执行的部分只是其中一小部分。如果不加上 session_state = 'ON CPU',统计数据会把各类无效等待样本一并纳入——结果是“响应慢的模块”被误认为是“CPU消耗高的模块”,导致性能优化方向出现偏差。
- 仅使用
MODULE = 'ORDER-SVC'过滤:得到的是该模块所有活跃会话的总采样数,包含等待、空闲、解析等各种状态。 - 增加
AND session_state = 'ON CPU'条件:才能真实反映该模块在CPU上实际执行的时间占比。 - 如果模块名来自JDBC的
setClientInfo方法,需要注意Oracle默认会截断为48字节,超出部分会被静默丢弃。
MODULE值的大小写与空格问题如何正确处理
Oracle对 MODULE 字段采用精确匹配方式,不会自动去除空格,也不忽略大小写。常见的错误场景是:应用代码中设置了 'payment-gw '(末尾包含空格)或 'Payment-GW',但查询时使用 = 'payment-gw',结果始终无法匹配到数据。
- 推荐的安全做法:使用
UPPER(TRIM(module)) = UPPER(TRIM('payment-gw'))进行不区分大小写且去除空格的比对。 - 先探查模块的实际存储值:执行
SELECT DISTINCT TRIM(module) FROM v$active_session_history WHERE sample_time > SYSDATE - 1/1440 AND module IS NOT NULL AND ROWNUM < 10,确认模块名的真实格式。 - 尽量避免使用
LIKE '%payment%'进行全模糊匹配——这种方式容易跨模块误命中,例如'PAYMENT-GW'和'REFUND-PAYMENT'都会被扫描出来,导致分析结果混乱。
查询历史模块CPU消耗必须使用DBA_HIST_ACTIVE_SESS_HISTORY
v$active_session_history 仅保留最近约1小时的内存数据。如果需要分析昨天下午3点 MODULE = 'INVOICE-BATCH' 的CPU使用情况,必须切换到磁盘归档视图。但这里有一个重要的限制:DBA_HIST_ACTIVE_SESS_HISTORY 中的 MODULE 字段只记录AWR快照时刻的值,并非每秒采样都会保留,因此精度会有所下降——它只能反映快照周期内(默认60分钟)该模块是否活跃过,无法捕捉瞬时的性能尖刺。
- 必须显式指定时间范围:使用
sample_time BETWEEN TIMESTAMP '2026-06-12 15:00:00' AND TIMESTAMP '2026-06-12 16:00:00',不要简单写成SYSDATE - 1。 - 在RAC环境下,该视图已经聚合了所有实例的数据,无需手动执行
UNION ALL;但可以通过instance_number字段下钻到单个节点进行分析。 - 如果发现历史视图中某个模块的
MODULE字段为空,很可能是应用程序根本没有设置该字段,或者设置了但被LogMiner、JOB等后台会话覆盖了(后台会话通常会清空MODULE)。
如何将MODULE与具体SQL_ID关联并排除干扰
一个模块可能同时运行数十条SQL语句。如果只知道 MODULE = 'USER-PROFILE' 占用了200个CPU样本,根本无法定位问题的根本原因。必须将 MODULE、SQL_ID、PLAN_HASH_VALUE 三者结合起来进行分析,同时排除解析操作、递归调用等非业务SQL的干扰。
- 添加
sql_id IS NOT NULL条件,过滤掉硬解析、PL/SQL执行等没有SQL_ID的行。 - 排除系统内部SQL:使用
AND sql_id NOT IN (SELECT sql_id FROM v$sql WHERE command_type IN (2, 3, 6))(其中2=INSERT, 3=SELECT, 6=UPDATE,其余多为递归或内部操作)。 - 同一个
sql_id可能对应多个不同的执行计划,必须加上sql_plan_hash_value进行分组,否则低效计划和高效计划混在一起统计,得出的结论将毫无意义。 - 示例查询语句:
SELECT module, sql_id, sql_plan_hash_value, COUNT(*) cpu_samples FROM dba_hist_active_sess_history WHERE module = 'USER-PROFILE' AND session_state = 'ON CPU' AND sample_time BETWEEN ... GROUP BY module, sql_id, sql_plan_hash_value ORDER BY cpu_samples DESC。
MODULE 是应用层可观测性的重要入口,但它本身并不携带执行上下文信息。定位到高CPU消耗的模块后,下一步必须深入到 SQL_ID + 执行计划 + 绑定变量 的组合分析——否则你优化的只是表面现象,而非真正的执行逻辑。
