Oracle数据库在日常运行中经常会出现间歇性性能下降,这类问题往往持续时间短、发生突然,传统的快照级报告很难捕捉到现场。ASH(Active Session History,活动会话历史)作为Oracle内置的轻量级诊断组件,以固定的时间间隔对数据库中处于非空闲状态的会话进行采样,并将这些会话的活动细节写入内存中的环形缓冲区。当系统出现响应变慢、CPU飙升或者锁等待加剧时,只要故障窗口落在ASH采样保留期内,我们就可以通过分析这些样本还原出当时每一个活跃会话在做什么、等什么以及被谁阻塞。

ASH的采样机制与核心数据结构
ASH的采样频率默认是每秒一次,由后台进程MMNL负责将活跃会话的信息写入SGA中的ASH缓冲区。所谓活跃会话,是指那些当前正在消耗CPU或者处于某种等待事件(如IO等待、锁等待、网络等待)中的会话,空闲会话不会被记录。这种只记录活跃会话的设计,既控制了数据量,又保证了在性能问题发生时能够拿到最有价值的现场信息。ASH缓冲区是一个固定大小的内存区域,当写满之后新的采样会覆盖最旧的记录,因此在默认配置下,ASH在SGA中通常只能保留几分钟到几十分钟的数据,具体时长取决于数据库负载和缓冲区大小。
除了内存中的V$ACTIVE_SESSION_HISTORY视图,Oracle还会定期将ASH缓冲区中的数据刷入磁盘,形成DBA_HIST_ACTIVE_SESS_HISTORY表,这部分数据会保留更长时间并作为AWR报告的数据源之一。我们在做实时诊断时主要查询V$ACTIVE_SESSION_HISTORY,它包含的字段非常丰富,例如SAMPLE_TIME采样时间、SESSION_ID会话编号、SQL_ID当前执行的SQL标识、EVENT等待事件名称、WAIT_CLASS等待类别、BLOCKING_SESSION阻塞者会话、TOP_LEVEL_SQL_ID以及SQL_PLAN_HASH_VALUE执行计划哈希值等。理解这些字段是后续分析的基础,特别是EVENT和BLOCKING_SESSION,前者告诉我们会话卡在哪里,后者让我们看清锁等待的源头。
与AWR相比,ASH更偏向于微观和会话级。AWR每小时生成一次快照,汇总的是整体资源消耗,容易掩盖短时间突发问题;而ASH每秒采样,能精确定位到故障发生的那几十秒里,是哪条SQL、哪个模块、哪种等待事件在拖累系统。在真实运维中,如果接到业务反馈某报表查询在上午十点零三分突然卡住,直接查那个时间点的ASH数据,往往比等下一个AWR快照高效得多。
基于等待事件与SQL的实用查询方法
最常见的ASH分析思路是按等待事件聚合,找出采样次数最多、也就是会话累计等待时间最长的事件类别。比如系统响应慢,我们怀疑是IO瓶颈,就可以统计某时间段内各类等待事件的样本分布。下面这段SQL演示了如何查询最近十五分钟内,各等待事件对应的活跃会话采样数:
SELECT
NVL(event, 'ON_CPU') AS wait_event,
COUNT(*) AS sample_count,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 2) AS pct
FROM v$active_session_history
WHERE sample_time >= SYSDATE - INTERVAL '15' MINUTE
GROUP BY event
ORDER BY sample_count DESC;
如果查询结果显示ON_CPU占比极高,说明大量会话在烧CPU,可能是糟糕的SQL做了全表扫描或大量计算;如果显示db file sequential read很多,则指向索引扫描类的IO等待;若是enq: TX - row lock contention突出,那就是典型的应用层行锁争用。拿到等待事件之后,下一步通常是关联SQL_ID,看看是哪些语句引发了这些等待。我们可以把上面结果中的热点事件与SQL_ID组合查询,定位到具体的慢SQL文本和执行计划。
另一种非常实用的分析是锁等待链追踪。当应用反馈更新操作卡死时,利用BLOCKING_SESSION字段可以快速画出谁阻塞了谁。以下示例找出当前被阻塞会话及其阻塞源,并附带它们正在执行的SQL:
SELECT
h.session_id AS blocked_sid,
h.blocking_session AS blocker_sid,
h.event AS wait_event,
h.sql_id AS blocked_sql,
b.sql_id AS blocker_sql
FROM v$active_session_history h
LEFT JOIN v$active_session_history b
ON h.blocking_session = b.session_id
AND h.sample_time = b.sample_time
WHERE h.blocking_session IS NOT NULL
AND h.sample_time >= SYSDATE - INTERVAL '10' MINUTE
ORDER BY h.sample_time DESC;
这类查询能直接暴露出阻塞链条,比如某个会话做了未提交的事务持有行锁,导致后续几十个会话堆积在enq: TX等待上。定位到阻塞源SQL_ID后,结合DBA_OBJECTS和应用日志,就能明确是哪张表、哪个业务逻辑忘记提交。相比盲目杀会话,用ASH数据说话可以最小化误杀风险。
结合AWR与日常监控的落地建议
虽然ASH本身数据保留短,但通过与AWR的协同可以形成长短结合的诊断体系。在日常监控中,我们可以配置脚本每五分钟抓取一次V$ACTIVE_SESSION_HISTORY的异常等待,一旦某种等待事件占比超过阈值就告警。对于历史疑难问题,则从DBA_HIST_ACTIVE_SESS_HISTORY中按更长周期回放,比如对比一周内每天上午高峰时段的TOP SQL变化。这种用法不需要在业务侧埋点,完全基于数据库自带能力,实施成本很低。
在落地时还要注意权限与开销问题。查询V$ACTIVE_SESSION_HISTORY需要SELECT_CATALOG_ROLE或相应系统权限,生产环境应分配给监控账号而非业务账号。ASH采样本身对数据库性能影响极小,但在高并发场景下如果频繁做大量GROUP BY查询,也会产生额外解析和CPU消耗,因此建议将分析SQL固化成视图或定时任务,避免在故障高发期由多人同时跑复杂ASH查询。另外,对于使用了多租户架构的Oracle环境,CDB和PDB级别的ASH视图名称略有差异,排查时要确认当前容器,防止查错数据源。
从架构思考的角度看,ASH本质上提供了一种低成本的分布式追踪思路:把每个会话当作一个请求链路节点,每秒打点。我们在设计自有系统监控时也可以借鉴,对关键线程或协程做轻量采样,记录其状态与阻塞对象,在出现毛刺时迅速回放。当团队逐渐积累起ASH分析的经验模板,例如常见的锁等待链、硬解析风暴、缓冲忙等待等模式,定位线上问题的平均耗时将从小时级压缩到几分钟,这对保障核心交易系统的稳定性具有实际价值。