Oracle AWR/ASH 实战——从等待事件到 SQL 调优的完整诊断链路

AWR 和 ASH 是 Oracle DBA 的"听诊器"和"心电图",但太多人只会看 Top 5 Timed Events,然后对着 wait class 发呆。我见过一个 DBA,AWR 报告里 db file sequential read 排第一,他直接说"加索引",结果加了三个索引,问题更严重了——因为根因不是缺索引,是统计信息过期导致优化器走了 Nested Loops,回表次数太多。这篇文章把 AWR/ASH 的核心指标、诊断链路、以及从等待事件定位到 SQL 调优的完整路径拆清楚,不讲虚的,直接上真实案例。

1 AWR 不是报告,是时间切片的历史回放

AWR(Automatic Workload Repository)默认每小时拍一张快照,记录这一小时内的累计统计信息。两张快照之间的差值,就是这一个小时内的"增量"。很多 DBA 只看单张 AWR 报告,其实单张报告里的数字是"从实例启动到现在的累计值",没有诊断价值。必须对比两张快照,看增量变化,才能定位问题窗口。比如 DB Time 从 1000 秒涨到 5000 秒,说明这个小时负载暴增;Executions 从 10 万涨到 50 万,说明 SQL 执行次数翻了 5 倍。AWR 的核心价值是"趋势分析",不是"绝对值"。

ASH(Active Session History)是 AWR 的补充,它每秒钟采样一次活动会话的状态,记录会话正在执行什么 SQL、等什么事件。ASH 的数据存在内存里(v$active_session_history),每小时刷到磁盘(dba_hist_active_sess_history)。ASH 的优势是"细粒度"——AWR 看的是小时级趋势,ASH 看的是秒级行为。比如 AWR 显示 db file sequential read 占 30% DB Time,ASH 可以告诉你这 30% 里,80% 集中在某条 SQL(sql_id=abc123),而且这条 SQL 在等索引块(p1=file#5, p2=block#67890)。这种细粒度定位,AWR 做不到,ASH 可以。

2 Top 5 Timed Events:不是看排名,是看比例和变化

AWR 报告第一页的 Top 5 Timed Events,很多 DBA 只看排名。实际上,排名本身意义不大,比例和变化才有意义。比如:Event A 占 DB Time 的 40%,Event B 占 30%。如果 Event A 是上小时的 10% 涨上来的,说明这是新出现的瓶颈;如果 Event B 一直是 30%,说明这是常态,不用急着处理。另外,要看 Waits / Txn(每事务等待次数)和 Time / Wait(每次等待时间)。比如 log file sync 占 25%,Waits/Txn=2.5,Time/Wait=15ms,说明每个事务提交 2.5 次(批量提交不积极),每次等 15ms(存储 I/O 慢)。根因有两个方向:应用层减少提交次数,或者存储层降低 fdatasync 延迟。如果只看"log file sync 25%",就盲目加 LOG_BUFFER,可能完全走错方向。

我们给一个客户做 AWR 分析时,Top 5 里 enq: TX - row lock contention 占 35%。新手 DBA 会说"行锁争用,加索引减少锁持有时间"。但我们看了 Waits/Txn=0.8,Time/Wait=450ms,说明不是锁持有时间长,是锁等待时间长——会话 A 持有锁 10ms,会话 B 等 450ms才拿到,说明并发事务在抢同一行。根因是业务逻辑:两个接口同时更新同一笔订单的状态,一个走主键更新(快),一个走状态+时间范围更新(慢且锁多行)。解决办法是改业务逻辑,让状态更新走统一入口,而不是加索引。这个案例说明:等待事件只是症状,不是病因。AWR 告诉你"哪里疼",ASH 告诉你"为什么疼"。

3 SQL Statistics:从执行计划到资源消耗的全景

AWR 报告的 SQL Statistics 部分,按不同维度排序:SQL ordered by Elapsed Time:总耗时最长的 SQL。注意这里的时间是"SQL 执行时间 + 等待时间",如果一条 SQL 执行 100 次,每次 1 秒,总耗时 100 秒;另一条 SQL 执行 1 次,耗时 90 秒,总耗时 90 秒。前者排名更高,但单次执行并不慢,优化价值可能不如后者。所以要看 Executions 和 Elapsed Time 的比值,以及 SQL 的业务重要性。SQL ordered by CPU Time:CPU 消耗最高的 SQL。这类 SQL 通常是计算密集型(大量函数调用、排序、聚合),优化方向是减少计算量或并行化。SQL ordered by Gets:逻辑读最高的 SQL。逻辑读 = 内存访问次数,高逻辑读意味着高 CPU 消耗(因为内存访问也耗 CPU),优化方向是减少访问的数据量(索引、分区剪枝、改写 SQL)。SQL ordered by Reads:物理读最高的 SQL。物理读 = 磁盘 I/O 次数,高物理读意味着数据不在 Buffer Cache 里,优化方向是扩大 Buffer Cache、优化访问路径、或者用 KEEP 池缓存热数据。

我们分析 AWR 时有个习惯:先看 Gets 和 Reads 的比例。如果 Gets 很高(比如 1 亿),但 Reads 很低(比如 1000),说明数据基本在内存里,瓶颈在 CPU 或解析;如果 Gets 和 Reads 都很高(比如 Gets=1 亿,Reads=8000 万),说明 Buffer Cache 命中率极低,可能是 SGA 太小、或者全表扫描太多、或者并发缓存争用严重。这个比例比单独的绝对值更有诊断价值。另外,要看 SQL 的 Plan Hash Value 是否稳定。如果同一条 SQL 在 AWR 里出现多个 Plan Hash Value,说明执行计划不稳定,需要检查统计信息、绑定变量、或者自动索引。

4 实验:用 AWR + ASH 定位一条慢 SQL 的根因

实验环境:Oracle 19c,模拟一个生产故障:某条核心报表 SQL 从 30 秒变成 8 分钟,业务方投诉。我们用 AWR 和 ASH 联合诊断,还原完整的排查链路。

-- 1. 生成 AWR 报告(问题窗口:9:00-10:00)
SELECT * FROM TABLE(DBMS_WORKLOAD_REPOSITORY.AWR_REPORT_HTML(
    l_dbid => 1234567890,
    l_inst_num => 1,
    l_bid => 150,  -- 9:00 快照
    l_eid => 151   -- 10:00 快照
));

-- 2. AWR 关键发现
-- Top 5 Timed Events:
-- 1. db file sequential read: 45% DB Time, Waits/Txn=850, Time/Wait=12ms
-- 2. CPU time: 30% DB Time
-- 3. log file sync: 8% DB Time

-- 3. SQL ordered by Elapsed Time:
-- SQL_ID: abc123, Elapsed Time: 480s, Executions: 12, Gets: 125000000
-- Plan Hash Value: 987654321(跟上周的 123456789 不同!)

-- 4. 用 ASH 细查这条 SQL 的等待分布
SELECT event, count(*) ash_samples,
       round(count(*)/sum(count(*)) over()*100,2) pct
FROM   v$active_session_history
WHERE  sql_id = 'abc123'
AND    sample_time BETWEEN SYSDATE - 1/24 AND SYSDATE
GROUP  BY event
ORDER  BY ash_samples DESC;
-- 结果:
-- db file sequential read: 450 samples, 78%
-- CPU + Wait for CPU: 85 samples, 15%
-- latch: cache buffers chains: 25 samples, 4%

-- 5. ASH 定位具体等待的对象
SELECT event, current_obj#, current_file#, current_block#, count(*)
FROM   v$active_session_history
WHERE  sql_id = 'abc123' AND event = 'db file sequential read'
GROUP  BY event, current_obj#, current_file#, current_block#
ORDER  BY count(*) DESC;
-- 结果:current_obj#=54321(orders 表),current_file#=5

-- 6. 查看执行计划变化
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR('abc123',
    format => 'ALLSTATS LAST +COST +BYTES +PREDICATE'));
-- 旧计划(上周):HASH JOIN + FULL TABLE SCAN (orders) + FULL TABLE SCAN (customers)
-- 新计划(今天):NESTED LOOPS + INDEX RANGE SCAN (orders_idx) + TABLE ACCESS BY INDEX ROWID

诊断链路还原:第一步,AWR Top 5 显示 db file sequential read 占 45%,说明大量单块读,可能是回表或索引扫描。第二步,SQL Statistics 发现 abc123 的 Elapsed Time 480 秒,Gets 1.25 亿,而且 Plan Hash Value 变了,说明执行计划跳变。第三步,ASH 确认这条 SQL 的 78% 时间在等 db file sequential read,而且集中在 orders 表(obj#=54321)。第四步,AWR 执行计划对比发现,旧计划是 HASH JOIN + FTS,新计划是 NESTED LOOPS + INDEX RANGE SCAN。第五步,查统计信息,发现 orders 表的 NUM_ROWS 还是 100 万(上周的值),但实际已经涨到 800 万(ETL 加载了 700 万行)。优化器以为 orders 表很小,选了 NESTED LOOPS + INDEX,但实际 orders 表现在很大,NESTED LOOPS 回表 800 万次,每次单块读,逻辑读 1.25 亿,物理读 850 万次,从 30 秒变成 8 分钟。解决办法:立即收集 orders 表统计信息,执行计划恢复为 HASH JOIN + FTS,SQL 回到 35 秒。

【踩坑笔记】AWR + ASH 联合诊断的黄金法则是:AWR 定位"哪个 SQL 慢",ASH 定位"这条 SQL 在等什么",执行计划对比定位"为什么慢",统计信息验证定位"根因是什么"。四层漏斗,缺一不可。只看 AWR 不看 ASH,只能知道症状;只看 ASH 不看执行计划,只能知道等待类型;只看执行计划不看统计信息,只能知道计划变了但不知道为什么变。

5 自定义 AWR 快照与基线对比

默认 AWR 每小时一个快照,但对于突发故障,一小时的分辨率太粗了。比如故障发生在 9:15-9:25,默认快照 9:00 和 10:00 会把故障窗口稀释在整个小时里,Top 5 的比例被拉平,看不出峰值。我们的做法是:故障发生时,立即手动创建快照,把故障窗口单独切片出来。

-- 手动创建 AWR 快照
EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT();

-- 查看快照列表
SELECT snap_id, begin_interval_time, end_interval_time
FROM   dba_hist_snapshot ORDER BY snap_id DESC;

-- 生成故障窗口的 AWR 报告(比如 9:15-9:25)
SELECT * FROM TABLE(DBMS_WORKLOAD_REPOSITORY.AWR_REPORT_HTML(
    l_dbid => 1234567890,
    l_inst_num => 1,
    l_bid => 152,
    l_eid => 153
));

-- 建立 AWR 基线(正常时段的参考)
EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_BASELINE(
    start_snap_id => 100,  -- 上周同一时段
    end_snap_id => 101,
    baseline_name => 'NORMAL_BASELINE'
);

-- 对比基线与故障窗口
SELECT stats_name, normal_value, incident_value,
       round((incident_value-normal_value)/normal_value*100,2) change_pct
FROM (
    SELECT 'DB Time' stats_name,
           (SELECT value FROM dba_hist_sysstat WHERE snap_id=100 AND stat_name='DB time') normal_value,
           (SELECT value FROM dba_hist_sysstat WHERE snap_id=152 AND stat_name='DB time') incident_value
    FROM dual
);

自定义快照 + 基线对比的价值是:把故障窗口从一小时压缩到 10 分钟,Top 5 的比例更尖锐,更容易定位峰值。而且基线对比可以量化"异常程度":比如 DB Time 比基线涨了 800%,Executions 涨了 50%,说明不是业务量变大了,是每条 SQL 的执行效率暴跌了。这种量化结论,比"感觉系统慢了"更有说服力,跟开发团队或领导汇报时,底气足得多。我们给赢禾科技内部定了个规矩:任何 P1 故障,必须在 5 分钟内创建 AWR 快照,故障恢复后 30 分钟内输出 AWR 对比报告,作为复盘材料。这个规矩执行了一年,故障根因定位时间从平均 4 小时降到 45 分钟。

6 总结:AWR/ASH 是 DBA 的显微镜,不是放大镜

AWR 和 ASH 的区别,我打个比方:AWR 是放大镜,看的是轮廓和趋势;ASH 是显微镜,看的是细胞和分子。两者配合,才能从"系统慢了"定位到"第 5 号数据文件的第 67890 号块被某条 SQL 读了 800 万次"。AWR/ASH 诊断的三板斧:第一板斧,Top 5 定方向。看比例、看变化、看 Waits/Txn 和 Time/Wait,确定是 I/O 瓶颈、CPU 瓶颈、还是锁争用。第二板斧,SQL Statistics 定嫌疑人。按 Elapsed Time、Gets、Reads 排序,找出资源消耗大户。第三板斧,ASH 定证据。看这条 SQL 的具体等待事件、等待对象、执行计划变化、统计信息时效性。三板斧砍完,80% 的性能问题都能定位到具体 SQL 和具体原因。最后送一句话:AWR 报告不是看完就扔的废纸,是数据库的"体检报告"。定期做(每周一次)、对比看(跟基线比)、深入挖(从事件到 SQL 到计划),才能防患于未然。别等故障发生了才想起翻 AWR,那时候已经是"尸检报告"了


请使用浏览器的分享功能分享到微信等