半夜被监控短信吵醒,屏幕上跳着“数据库I/O等待过高”的告警,这种事情干DBA的都会碰上。尤其是环境换了Oracle 19c之后,很多老经验要重新校准,等上了云RDS,又发现以前能直接查的系统视图、能tail的日志、能碰的存储层,突然全变成“黑盒”了。这篇就围绕Oracle 19c和云RDS环境下的I/O问题排查与归档,把我实际踩过、试过、反复验证过的路径完整梳理一遍。内容适合正在从传统运维转向云环境的同行,也适合刚接手Oracle 19c、一遇到I/O告警就头疼的入门DBA。
搞I/O排查,最忌讳一上来就抓iostat、盯着%util喊存储故障。我见过太多误判:表面看磁盘忙到100%,实际上问题出在日志切换、糟糕的SQL把缓冲池打穿,甚至只是归档目录写满导致的假象。正确的姿势应该是从“数据库等待事件”这个源头切入,再用系统层数据做交叉验证,最后才判断到底是存储、链路、SQL还是数据库内部机制的问题。这套方法论放到Oracle 19c和云RDS上都成立,但细节处理有不少差异。
1. I/O问题排查的整体思路与方案选型
1.1 先分清“数据库卡”和“存储慢”的本质区别
很多人把I/O问题简单理解为“存储设备性能不行”,这是最大的误区。数据库侧感知到的慢,可能来自多个环节:SQL执行计划变差导致读入的数据量暴增、redo日志切换太频繁、归档进程跟不上日志生成速度、表空间扩容触发数据文件自动扩展、甚至是因为锁等待造成的假I/O压力。这些情况里,存储本身并没有“慢”,是数据库自己制造了额外、不必要的I/O请求。
反过来,如果存储层真的出问题(响应时间变长、丢盘、链路异常),数据库侧的表现一定是等待事件集中爆发,比如Oracle 19c里常见的db file sequential read、db file scattered read、log file parallel write大面积堆积。所以排查的第一步不是去看磁盘跑没跑满,而是打开AWR或实时ASH,先回答一个问题:这些I/O等待事件占比是多少、集中在哪类等待上、关联的SQL或对象是什么。这一步能直接决定后续往哪个方向查。
1.2 建立三层信息收集框架,避免只盯单一指标
我的做法是把排查信息分三层收集,缺一不可。第一层是数据库视图与报告,包括AWR、ASH、v$session_wait、v$system_event和v$sgastat。这一层提供的是“数据库视角”,告诉你等待事件、TOP SQL、对象统计、日志切换频率。第二层是OS级监控,包括iostat -x、sar -d、top/pidstat、ps中进程状态和strace(如果条件允许)。这一层提供的是“物理视角”,告诉你块设备层面的IOPS、吞吐、await、svctm、队列长度。第三层是云平台侧的信息,比如云RDS控制台里的监控项、性能洞察、慢日志、事件通知。
在传统机房环境里,三层都能拿到,网络是通的、是能SSH上去看的;到了云RDS环境,OS层会被封掉,strace、iostat这类命令不可用,只能依赖第一层和第三层,而且很多数据库参数也改不了。这种“黑盒”形势下,AWR/ASH的定位价值反而被放大,你必须更熟练地用等待事件去推断OS行为,再用云平台的监控曲线去验证。
1.3 为什么在19c上要特别注意I/O相关的默认变化
从11g、12c升级到Oracle 19c后,我明显感觉到几个和I/O相关的行为变化。一是多租户架构(CDB/PDB)变成默认选择,租户数多了之后,共享Undo和Temp带来的I/O竞争问题更突出。二是自动维护任务(比如自动统计信息收集、自动优化)依然会在夜间集中扫描大表,如果你把维护窗口设置不合理,19c的自动任务甚至比11g更容易在凌晨制造I/O陡增。三是19c对异步I/O、Direct Path Read的使用更激进,对大表的全表扫描常常绕开buffer cache直接读盘,OS层体现为db file sequential read等待变多,但实际效率和总量其实可控。
这些变化决定了排查19c时,不能完全照搬老套路。比如老DBA一看db file scattered read多就急着优化SQL,但在19c的新优化器行为下,某些Direct Path Read本来就是正常路径。先确认执行计划、看SQL是否合理,再决定动不动,这一点后面详细展开。
2. 数据库视角的I/O体检:AWR与ASH的正确打开方式
2.1 AWR报告里真正要看的几个指标和段位
打开AWR报告,不要急着看前几页的头部摘要,先抓几个核心数字。第一个是Elapsed/DB Time,你要记住一个简单换算:当DB Time明显高于Elapsed Time数倍时,说明系统处于长时间高负载,此时I/O问题可能是果而不是因。第二个是Top 5 Timed Events,如果db file sequential read或Direct Path Read占比超过30%,基本可以认定物理读环节出了问题,下一步直接查SQL ordered by Reads部分。
第三个关键段位是IO Profile区域(在新版AWR里叫IO Statistics之类的名字),这里包含了每秒读写次数(Read IOPS/Write IOPS)、每秒读写字节数、每秒重做生成量。记住两个经验值:单块读的平均响应时间超过20ms就该警惕,超过50ms基本可以判定存储链路异常或磁盘“生病”;每秒redo生成量如果长期超过50MB,需要注意归档和网络传输的压力。第四个要关注Tablespace IO Stats,这里能按表空间维度看到读写的物理量,能快速判断到底是SYSTEM表空间、Undo还是业务数据文件导致的I/O压力。
看AWR的常见误区是把Elapsed Time当成性能指标本身。其实Elapsed Time只是报告覆盖的时间段,重要的是期间DB Time是上升还是下降、与CPU时间的比例。我见过有人拿一份‘运行了4小时、DB Time 20分钟’的AWR说系统很忙,这种判断显然是错误不用心的。
2.2 ASH实时排查:用一分钟粒度定位等待来源
如果问题正在发生,等AWR报告跑完已经太迟了,这时候直接用ASH。Oracle 19c的v$session和v$active_session_history的字段很丰富,我一般会按事件维度快速聚合。下面这个SQL是起手动作,先把当前会话的等待事件按数量排出来:
select event, count(*) cnt, round(count(*)/sum(count(*)) over()*100, 1) pct from v$session where type != 'BACKGROUND' and wait_class != 'Idle' group by event order by cnt desc;如果当前卡的是I/O等待,比如db file sequential read排第一,再往下钻一层,查具体在等哪个对象、对应的SQL是什么:
select s.sid, s.serial#, s.username, s.sql_id, s.event, o.owner||'.'||o.object_name obj, s.BLOCKING_SESSION from v$session s left join dba_objects o on o.data_object_id = s.ROW_WAIT_OBJ# where s.event like 'db file%read%' order by s.sid;然后拿SQL_ID去v$sql里取完整文本,分析执行计划是否走偏、条件列上有没有索引、是不是被绑定变量窥探坑了。注意ASH在RDS高负载时采样可能丢数据,如果发现sample_time间隔异常,优先从云平台侧拉监控曲线补数据。
2.3 19c特定对象级统计信息:UNDO与临时表空间别漏掉
很多I/O排查只盯着业务表,忽略Undo表空间和临时表空间的读写。19c在多租户下Undo管理更加敏感,一个PDB里的长事务、大查询产生的Undo量可能拖累整个CDB。看v$undostat里的undoblks、maxquerylen,如果maxquerylen高到接近Undo retention,说明可能有长查询未提交,Undo的写盘量会持续放大。临时表空间方面,重点查v$tempseg_usage,如果大量sort/hash join发生在临时段,会产生direct tempfile write等待,这种I/O压力跟数据文件读是两种不同的解决路径(通常要优化SQL,而不是换存储)。
还有一个小细节:19c自动维护任务在夜间收集统计信息时,如果使用concurrent statistics collection对多个表并行采样,I/O会突然抬高。碰到这种夜间尖峰,不要慌,先看是否与DBMS_SCHEDULER维护窗口时间重合,若是,把维护窗口挪到业务低谷即可,大多数情况下没必要因为夜间一个波动去换硬盘。
3. 系统与存储视角:传统命令还能用多少,云RDS下看什么
3.1 OS层常用命令的“正确”读法
如果能登录OS(比如自建19c物理机/虚拟机场景),优先用iostat -x 1连续观察。重点不是%util,而是await、svctm和aqu-sz(平均队列长度)。await是发出IO请求到返回的总时长,svctm是设备实际处理时间,两者之差可以理解为排队时间。用银行柜台类比:svctm是柜员办一笔业务的时间,await则是你从取号到办完离开的时间。一个盘await高但svctm不高,说明系统调度或队列拥堵;两者都高,大概率设备本身有问题。
r/s和w/s分别代表每秒读、写请求次数,适合判断是“小数据量大次数”还是“大数据量小次数”。如果r/s很高但rkB/s很低,一个请求才几KB,这是典型的随机读特征,通常配合SQL优化来治理;如果rkB/s很高但r/s不高,是顺序扫描,要考虑是不是全表扫描绕开了索引。
pidstat -d 1可以按进程维度看IO读写量,Oracle的进程名都带着号,比如ora_dbw0_<SID>是DBWR、ora_ckpt_<SID>是检查点进程、ora_arc0_<SID>是归档进程。如果pwrite、pread的耗时集中在某个进程上,直接就能定位到是写数据文件、写归档还是读写控制文件的压力。更高阶一点可以用strace -p <pid> -e trace=write观察系统调用的细节,但生产环境慎用,strace会严重拖慢I/O,我一般只在问题可复现的测试环境用。
3.2 别被%util骗了,IOPS和时延才是关键
很多监控系统喜欢把磁盘使用率(%util)画成仪表盘,但单看这个数字非常容易误判。%util高的真正含义是“设备在采样周期内处于忙状态的时间占比”。一块盘在持续处理不确定但分散的少量IO时,%util可能很漂亮;反之如果每个IO本身就慢,%util其实不一定高(比如它主要时间不在处理,而在等待)。业界常用的判断标准是看时延:单块读15ms以内基本健康,15~20ms提示性能下降,超过30ms就要立即行动。云RDS上的概念也类似,不要光看CPU/负载曲线,要看读写延迟和IOPS是否打满。
判断“打满”的另一个标准是排队。Oracle的v$system_event里如果io queueing等待类的总和变大,说明请求已经排队,系统资源接近上限。这个时候再回头看OS层的aqu-sz是否乘数增长,就能确认是设备吞吐上限而不是偶发抖动。
3.3 云RDS的特殊限制:黑盒环境下如何做交叉验证
云RDS最大的难点在于“黑盒”。你无法看到OS、无法正常执行iostat,但并不意味着完全没有判断依据。以阿里云RDS为例(其他云平台类似),控制台里的“增强监控”会提供磁盘IOPS、吞吐量、延迟、连接数等多维曲线,按5秒或更短粒度展示。排查I/O问题时,我会先把数据库等待事件的时间点,与云监控上的读延迟/写延迟曲线对齐,再与IOPS/吞吐曲线对齐。如果延迟高而IOPS并不高,可能是单次IO队列过长;如果IOPS已经接近实例规格上限,就需要升配。
另一个不可忽视的云特性是“IOPS信用机制”。很多云RDS的标配IOPS是基础值,超过后会爆发式使用“积分”,积分耗尽就产生性能悬崖。典型场景是:白天业务平缓,晚上跑批量任务IOPS冲高,原本以为能维持几分钟的时间,结果积分耗尽、磁盘延迟瞬间拉高。排查时如果发现延迟曲线呈现“长期平稳+尖峰”的特征,优先查看该实例的IOPS burst额度是否耗尽。
RDS环境下的strace、perf等OS级手段全部不可用,要充分利用数据库内部的虚拟视图,比如查v$iostat_file,按文件看物理读写的情况(这是Oracle 19c提供的好工具,即使RDS也能查)。经验是先用v$iostat_file缩小范围到具体的表空间/文件类型,再用云监控做容量层面的验证。
4. 归档日志与I/O的纠缠:一个常被忽视的隐形杀手
4.1 归档日志为什么能拖垮一个库的I/O
Oracle数据库在ARCHIVELOG模式下,一个redo log组写满后触发切换,ARCn进程要把redo日志复制成归档日志写到归档目录。如果归档目录速度慢、空间不足、或者切换频率过高,log file switch会成为大量会话的等待事件,Oracle 19c里表现为log file switch (archiving needed)和log file switch completion堆在Top Events里。
我曾经遇到过一个生产库,业务高峰每5分钟切换一次redo,每次切换要copy 2GB的归档文件到共享存储。那个共享存储的写延迟本身有30ms,ARCn每写一次都排很久队,redo切换等着ARCn copy完才能继续写新redo,所有产生redo的会话全部堵成log file switch等待。从AWR看Top事件前三名全是“log file switch”系列,但很多人看到归档就只想到赶紧清理归档日志,实际上根源是归档的目标位置和redo切换频率不匹配。
4.2 排查归档环节的具体SQL和思路
归档相关的排查分三步走。第一步,查当前日志切换的频率和趋势:
select to_char(first_time, 'yyyy-mm-dd hh24') hour, count(*) switch_count from v$log_history where first_time > sysdate - 7 group by to_char(first_time, 'yyyy-mm-dd hh24') order by 1;如果某个时段switch_count远超平时(比如平时10次/小时,高峰变成100次/小时),说明redo生成量爆炸了,这通常意味着有大事务、频繁提交或者批量操作在大量产生日志。第二步,查归档目标目录的写性能和控制设置:
select dest_id, destination, status, error, gv$archive_dest from v$archive_dest where status in ('ACTIVE','ERROR');ERROR状态要重点查,看error列里报的是磁盘满、权限问题还是网络不通。第三步,查FRA(快速闪回区)空间使用,因为很多环境把归档和闪回日志都放在FRA里,空间满了后会直接阻塞所有写操作:
select name, space_limit/1024/1024/1024 as limit_gb, space_used/1024/1024/1024 as used_gb, space_reclaimable/1024/1024/1024 as reclaimable_gb from v$recovery_file_dest;4.3 常用治理手段:调参、换位置、控制归档进程数量
说几个实际的操作方向。第一,调整redo日志大小和组数。日志太小会导致切换过于频繁,每一步切换都是一次I/O小风暴。针对19c环境,我通常建议把redo log设成能覆盖“高峰15-30分钟生成量”的大小,过小不如不调。第二,使用LOG_ARCHIVE_DEST_*多路复用位置时要小心:多个归档目的地如果是同步写,任何一个慢都会阻塞主库。19c里可以针对某个目的地单独设置REOPEN和延迟容忍度,不能让慢的备用目的地拖了主库的后腿。第三,控制归档进程数量,log_archive_max_processes默认一般是4,如果归档量大可以调大,但不必盲目设到10以上,ARCn进程多了之后,反而会与DBWR争抢I/O。
RDS环境下,很多参数是封禁的,比如你改不了redo组数,也直接改不了log_archive_max_processes。这时候治理路径更偏向于:检查控制台里是否开启了“日志备份到对象存储”之类的功能,如果自动备份的频率过于频繁,或者备份保留时长过长,都会额外占用实例I/O。这种场景下真正有效的做法是联系云厂商工单,让后台优化备份策略,或者把备份时间窗口挪出业务高峰。
4.4 一个“归档误判”的经典案例复盘
有一次某客户环境报“I/O性能告警”,存储厂商和云平台都建议扩容IOPS。我登录数据库后用AWR一查,log file sync和log file parallel write占总等待的60%以上,redo size达到每秒80MB。再翻v$log_history,发现每天凌晨的切换次数是白天的20倍。找一个峰值时间段用v$session追踪,发现是某个PDB里的夜批程序在循环提交,每次都commit,redo搞到爆炸。最后方案很简单:让开发把循环里的逐条提交改成批量提交,redo生成量降了70%,I/O告警消失。这个故事的核心教训就是——很多看起来像存储的问题,根子在提交方式和日志机制上。如果当时听了存储厂商的推荐直接扩容,不仅花钱,还治不了病根。
5. 实操总结:一套顺手好用的排查流程与避坑清单
5.1 通用排查流程速查
我把整套流程总结成六个连贯动作,遇到I/O告警时按顺序执行,基本能把问题锁定在合理范围内。第一步,确认告警级别和时间窗口,打开云监控或zabbix看曲线,确认是不是真的有问题(有些告警阈值设太敏感,虚警太多)。第二步,连上数据库跑v$session聚合SQL和AWR快照,锁定Top等待事件,拿到SQL_ID和时间段。第三步,如果等待事件明确关联到数据文件物理读,看一下v$iostat_file或dba_data_files,对比v$filestat的统计,找到压力文件。第四步,交叉验证OS(或云监控)的延迟、队列、IOPS曲线,判断是设备级问题还是请求量问题。第五步,检查redo切换、归档目标、FRA空间,排除日志机制引发的人为I/O“放大镜”。最后一步,如果SQL是元凶,改SQL、补索引、调执行计划;如果是存储或实例规格瓶颈,考虑扩容或迁移。
这个流程最大价值是每一步都基于上一层的判断,不跳步、不事先给存储“定罪”。我在自建环境和RDS上都验证过多次,用这个方法最少能帮客户少走至少一半弯路。
5.2 常见问题速查表
| 现象 | 优先怀疑方向 | 第一步动作 |
|---|---|---|
| 单块读20ms以上,IOPS不高 | 存储链路或磁盘健康状况 | 查v$system_event和云监控延迟曲线 |
| 夜间固定时间段I/O高 | 自动维护任务或定时批处理 | 看维护窗口与v$log_history对照 |
log file switch等待多 | redo日志大小/切换频率/归档慢 | 查切换频率,评估redo组大小 |
direct path read占比大 | 并行扫描/Direct Path Read行为 | 抓SQL执行计划,评估并行度 |
| 紧急告警但AWR显示DB Time很低 | 监控阈值问题,不必过度反应 | 核对采样周期和阈值配置 |
| 归档目录空间不足 | 备份策略、FRA空间、归档保留 | 查v$recovery_file_dest和备份任务 |
5.3 避坑清单与个人体悟
第一,一定要先备份AWR/ASH报告或快照。很多时候排查到一半,告警自动恢复了,回头要用证据链时发现报告过期被覆盖了,这个疼我受过多次。碰到I/O异常,第一时间手动生成一对AWR快照(如果时间跨度明显),再导出相关视图数据留底。第二,注意v$session里的wait_class过滤,别把Idle事件当回事,否则统计出来的TOP事件会被SQL*Net message from client这类无害等待污染。第三,云RDS上做alter system前先看参数是否Modifiable,很多19c参数在RDS侧运营着限制,可以直接show parameter再查v$parameter的IsModifiable字段。第四,归档日志问题有一个比较隐蔽的表现:虽然归档目标还没报ERROR,但归档目录所在的磁盘卷空间使用率已经到85%,写归档的速度开始变慢,甚至块分配时发生元数据更新竞争,这种情况OS命令不一定能看到明显的%util高,但redo切换的等待已经开始堆积。及时清理过期归档、预留至少20%余量是经验法则。
说穿了,I/O排查这件事,本质上是“数据库内部机制、OS调度、存储能力、应用行为”四方的博弈。不同的环境封掉了不同的观测维度,但Oracle 19c里大量等待事件和动态视图仍然是最可靠的信号源。我自己排障的过程里,几乎每次焦灼到最后,都是从一行v$session或一份AWR里找到了突破口。你把数据库口给吃透了,无论在物理机还是云RDS上,I/O问题大概率都能在半小时内锁定方向——剩下的,就是跟存储厂商或云厂商工单的拉锯战了。