前言
最近在分析一套 Oracle 19c RAC 数据库的性能问题,业务反馈某个生产系统偶尔出现接口超时。
根据业务反馈的异常时间段,抓取了两份 AWR 报告。先看了 DB Time、CPU、Load Profile,整体负载并不高。两个实例的平均 AAS 都在 1 ~ 2 左右,CPU 也比较空闲。
继续往下看,就到了等待事件:
可以看到有很多等待事件:db file sequential read、read by other session、log file sync、gc cr grant 2-way……
Oracle 的等待事件非常多,尤其是 RAC 环境下,各种gc开头的事件,看着差不多,实际含义却不完全一样。
刚开始接触 AWR 时,我也想过把常见的等待事件整理出来,方便平时查阅。但后来发现,单纯记住事件名称和含义,实际帮助并没有想象中那么大。
比如看到db file sequential read,知道它是单块读,但到底是存储响应慢,还是 SQL 读取的数据太多?光知道事件的定义,显然还不够。
所以这篇简单聊聊,我平时是怎么理解和记忆 Oracle 等待事件的。
一、先从名字入手
Oracle 很多等待事件的名称,其实已经告诉了我们大致的排查方向。
| 关键词 | 大致方向 |
|---|---|
| db file | 数据文件 I/O |
| direct path | 直接路径 I/O |
| log file | Redo、日志写入、事务提交 |
| enq | 锁、队列资源争用 |
| gc | RAC Global Cache |
| library cache | 共享池对象相关 |
| cursor | 游标相关 |
| read by other session | 等待其他会话读取数据块 |
当然,这种分类并不严谨,只是方便平时快速判断。
比如gc current block 2-way,一看gc就知道和 RAC 的 Cache Fusion 有关;enq: TX - row lock contention通常要往事务锁的方向排查;而log file sync,则主要关注事务提交和 Redo 写入。
这样至少能先确定排查方向,至于具体的等待机制、参数含义,遇到了再进一步查也不迟。
二、I/O 相关的等待,先记这几个
最常见的三个:
db file sequential read db file scattered read direct path readdb file sequential read主要是单块读,常见于索引扫描、ROWID 回表等操作;db file scattered read主要是多块读,通常与全表扫描有关;direct path read则是直接路径读取,大表扫描、并行查询时比较常见。
这几个事件都比较常见,但实际分析时不能只看事件名称。
比如db file sequential read排在 AWR 等待事件前面,很多人第一反应就是磁盘 I/O 有问题,其实不一定。
这次数据库的 AWR 中,两个实例的db file sequential read平均等待时间都不到 1ms,单次 I/O 响应并不慢。但继续分析 SQL 后发现,有些 SQL 每次执行需要读取大量数据块,虽然单次读取很快,累计下来耗时依然很长。
其中一条 SQL 的统计信息如下(数值已做近似处理):
SQL_ID 已隐藏 执行次数 少于 10 次 平均执行时间 超过 100 秒 平均逻辑读 数十万块 平均物理读 十万块以上SQL 访问的是一张业务明细表,主要根据多个业务条件进行汇总查询。
执行计划如下(对象名称已脱敏):
SORT GROUP BY NOSORT INLIST ITERATOR TABLE ACCESS BY INDEX ROWID BUSINESS_TABLE INDEX RANGE SCAN IDX_BUSINESS_01从执行计划来看,这条 SQL 虽然走了索引,但单次执行仍然产生了大量逻辑读和物理读。
其中INDEX RANGE SCAN预估返回百万级记录,后续还需要通过 ROWID 回表。这里的行数是优化器估算值,实际扫描了多少,还需要结合执行统计进一步确认。
不过,单从 AWR 的数据来看,这条 SQL 的读取量已经明显偏高。
这也说明,db file sequential read等待较多,并不一定是存储性能问题,也可能是 SQL 本身访问了太多数据块。所以分析 I/O 等待时,除了关注单次 I/O 延迟,还要结合 SQL 的逻辑读、物理读和执行计划一起看。
db file scattered read和direct path read也是一样,重点还是看实际扫描了多少数据,以及这些读取是否必要。
毕竟,走了索引不代表 SQL 一定快,全表扫描也不代表执行计划一定有问题。
三、Commit 相关的等待
这类先熟悉两个就够了:
log file sync log file parallel writelog file sync主要是前台会话等待事务提交完成,log file parallel write则是 LGWR 写入 Redo 日志时产生的等待。
这两个事件通常需要结合起来分析:如果log file sync和log file parallel write的平均等待时间都明显升高,就需要重点检查 Redo 日志写入延迟。
如果log file sync较高,而log file parallel write比较正常,就不能直接判断是存储问题,还需要结合 Commit 次数、LGWR 调度等情况进一步排查。
另外,应用频繁提交事务也值得关注:比如每处理一条记录就执行一次 Commit,即使单次提交耗时不高,累计等待时间也可能比较明显。
因此,分析log file sync时,不能只看累计等待时间,还要结合平均等待时间、提交频率以及log file parallel write的表现,判断究竟是提交过于频繁,还是 Redo 写入本身存在延迟。
四、锁和并发相关的等待
最常见的是:
enq: TX - row lock contention比如会话 A 更新一条记录后一直没有提交,会话 B 又来修改同一条记录,就可能产生等待。
不过,enq: TX - row lock contention并不一定都是普通的行锁阻塞,也可能涉及唯一键冲突、ITL 资源不足等情况。实际排查时,可以先通过GV$SESSION查看阻塞会话,再结合事务信息、等待模式和 SQL 进一步分析。
另外,还有一个比较常见的等待事件:
read by other session这个事件和行锁等待不是一回事,它表示当前会话需要读取的数据块正在由其他会话读取,因此需要等待对方完成 I/O。
前面提到的那条高读取量 SQL,在 ASH 中就出现了这类等待。
所以碰到并发等待时,还是要先区分具体的等待资源,enq: TX - row lock contention重点排查事务锁,而read by other session则需要关注并发读取、热点数据块以及相关 SQL 的 I/O 情况。
五、RAC 里面的 gc 等待
在 RAC 环境中,gc开头的等待事件也比较常见,主要与实例之间的 Global Cache(全局缓存)协调有关。
比如:
gc cr block 2-way gc current block 2-way gc cr grant 2-way这几个等待事件名称比较相似,可以拆开来看:
gc:Global Cache,RAC 全局缓存。cr:Consistent Read,一致性读。current:当前版本的数据块。block:通常涉及跨实例数据块传输。grant:主要涉及全局缓存访问授权,不一定需要传输数据块。
至于后面的2-way、3-way,主要用于区分不同的消息交互路径。
需要注意的是,RAC 本身就存在跨实例数据块访问,因此出现 gc 等待并不代表集群有问题。
实际分析时,我一般会结合等待次数、平均等待时间和数据块传输量来判断。如果等待时间明显偏高,再进一步检查相关 SQL、热点对象、跨实例访问情况以及私有网络延迟。
六、等待事件到底怎么看?
前面说了这么多,其实平时看 AWR,我主要关注以下几个指标:
| 指标 | 含义 |
|---|---|
| Waits | 等待次数 |
| Time(s) | 累计等待时间 |
| Avg wait | 平均每次等待时间 |
| % DB time | 累计等待时间占 DB Time 的比例 |
这里需要注意,等待次数多不一定代表问题严重,平均等待时间高也不一定说明对数据库整体影响很大。
比如一个事件发生了 100 万次,平均等待 0.1ms,累计等待时间为 100 秒;另一个事件只发生了 100 次,但平均等待 2 秒,累计等待时间反而达到 200 秒。
因此,分析时需要结合等待次数、累计等待时间和平均等待时间,不能只看其中一个指标。
另外,还要注意 AWR 的统计时间范围。这次数据库的 AWR 覆盖了数小时,两个实例的平均 AAS 都比较低,db file sequential read的平均等待时间也不到 1ms。
但业务反馈的是偶发超时,长时间窗口的平均数据很可能掩盖短时间内的性能波动。
对于这种情况,还需要结合业务异常的具体时间,通过 ASH 查看当时的活跃会话、SQL_ID 和等待事件,进一步确认问题发生在哪个环节。
七、遇到不认识的事件怎么办?
这个很简单,直接查。
Oracle 自己就提供了等待事件信息:
SELECT name, wait_class, parameter1, parameter2, parameter3 FROM v$event_name WHERE name = 'db file sequential read';查询结果:
NAME WAIT_CLASS PARAMETER1 PARAMETER2 PARAMETER3 ----------------------- ---------- ---------- ---------- ---------- db file sequential read User I/O file# block# blocks可以查看事件所属的 Wait Class,以及 P1、P2、P3 对应的参数含义。
如果想看看数据库当前累计等待时间较高的事件:
SELECT event, total_waits, ROUND(time_waited_micro / 1000000, 2) time_sec, ROUND( time_waited_micro / NULLIF(total_waits, 0) / 1000, 3 ) avg_wait_ms FROM v$system_event WHERE wait_class <> 'Idle' ORDER BY time_waited_micro DESC FETCH FIRST 20 ROWS ONLY;下面是部分常见等待事件的示例输出(演示数据,非生产查询结果):
EVENT TOTAL_WAITS TIME_SEC AVG_WAIT_MS ----------------------------- ------------ -------- ----------- db file sequential read 12500000 8750.00 0.700 log file sync 3200000 4800.00 1.500 gc current block 2-way 4800000 2400.00 0.500 log file parallel write 2500000 2500.00 1.000 direct path read 1800000 2700.00 1.500 read by other session 1500000 1200.00 0.800 gc cr block 2-way 2100000 1050.00 0.500需要注意的是,V$SYSTEM_EVENT记录的是实例启动以来的累计等待统计,并不是指定时间段的数据。如果要分析某个历史时段的性能问题,还是需要结合 AWR、ASH 进行排查。
对于不熟悉的等待事件,可以先通过V$EVENT_NAME查看事件分类和等待参数,再结合具体 SQL、会话及执行计划分析。平时遇到有代表性的案例,也可以顺手记录一下,后续碰到类似问题直接参考即可。
最后
Oracle 等待事件虽然很多,但平时真正经常遇到的也就那么一些。像 I/O、Commit、锁和 RAC 相关的等待,结合实际故障多分析几次,基本就熟悉了。
其他不常见的事件,遇到了再查也不迟。关键还是要结合等待时间、SQL 和执行计划,判断问题到底出在哪里,而不是单纯根据等待事件的名称下结论。