ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Oracle 19c云RDS I/O瓶颈排查:从等待事件到归档日志实战

Oracle 19c云RDS I/O瓶颈排查:从等待事件到归档日志实战 上周帮一个客户排查 Oracle 19c 云 RDS 实例的 I/O 问题现象很典型业务高峰时 SQL 偶发卡顿应用侧超时告警控制台上的 I/O 监控曲线明显抬升归档目录的积压也比平时多。客户的第一反应是数据库磁盘扩容但我在 AWR 里翻了翻等待事件发现真正严重的不是存储吞吐而是某段窗口内 redo 生成量暴增、归档进程跟不上、自动备份又恰好扫全库几个因素叠在一起把 I/O 链路堵住了。这类问题我在自建的 19c 单机和云 RDS 上都处理过不少经验算是比较成体系先把问题分层再把指标量化最后才动手调。这篇文章会把我的完整排查思路、常用 SQL 和踩过的坑整理出来适合正在维护 Oracle 19c 或云 RDS 的 DBA、运维和偏后端的开发同学参考。无论是磁盘真的慢、SQL 跑得太凶还是归档和备份把 I/O 挤爆按这套流程走一遍基本能有个明确结论。1. 别急着调存储先判断 I/O 等待是不是那个真凶很多同学遇到数据库慢第一件事就是查 AWR 里的 Top Timed Events看到User I/O类的等待排在前排立刻下结论磁盘慢升配。这个判断其实把因果搞反了。I/O 等待只是一个结果它的上游可能是 SQL 产生了大量不合理的物理读可能是 redo 量太大导致 LGWR 写不过来也可能只是锁等待被错误归类到了 I/O 上。Oracle 通过等待类帮我们做了初步分层19c 里最常见的有User I/O、System I/O、Commit、Application、Concurrency、Configuration、CPU。当Application或Concurrency占比很高时I/O 大概率只是在背锅。1.1 一个典型的 19c 慢库现象和真相可能完全不一样我遇到过这样一个 19c 库应用反馈整体变慢数据库层log file sync等待非常高。表面上看这是 commit 相关的等待算是 I/O 的一部分但抓取会话后我才发现应用是一行一条 INSERT并且每行都执行一次COMMIT一个批处理 50 万行就等于 50 万次提交每次都逼着 LGWR 把 redo buffer 里的内容刷到在线日志。后来应用改成绑定数组、每 1000 行提交一次log file sync几乎从 Top 等待里消失磁盘根本不需要动。这个例子说明等待事件只给你一个入口真正的病灶可能在代码里。所以我在定位问题时有个习惯性的三步问法这个库是慢还是忙还是在等 I/O如果是在等 I/O等待的是哪种 I/O次数多还是单次延迟高这些 I/O 是用户 SQL 触发的还是后台进程归档、备份、DBWR触发的把这三点回答完基本就不会被表面的等待事件带偏。1.2 优先认准几个等待事件和等待类有些同学看到几十个等待事件就蒙了。其实日常排查真正要看的是下面这几个其他大部分都可以先放一边。等待事件等待类常见触发表述优先排查方向db file sequential readUser I/O索引扫描或 ROWID 单块读平均延迟、单块读次数、SQL 访问路径db file scattered readUser I/O全表扫描、多块读执行计划是否走了不必要的全扫direct path readUser I/O直接路径读排序、并行、分区裁剪、大对象访问SQL 并发、临时表空间、19c 自动 direct readlog file syncCommit用户提交时等待 redo 落盘commit 频率、应用事务粒度、redo 写入延迟log file parallel writeUser I/OLGWR 并行写在线日志redo 量、归档争抢、存储写延迟log file switch (archiving needed)Configuration归档跟不上redo 切换被堵归档目标状态、ARCH 进程、redo 生成速率enq: TX - row lock contentionApplication行锁竞争阻塞会话、并发修改同一行这几个事件值得重点关注前三个指向数据文件读第四第五个指向 redo 写第六个直接指向归档第七个根本不属于 I/O。看到enq: TX却去升级磁盘是我见过最多也是最冤的操作。1.3 一张自检表区分 CPU、锁、I/O、网络光说理论太虚我整理了一张快速自检表排查时可以对着看看到的现象优先怀疑第一步做什么CPU 使用率接近 100%CPU 或并发看 ASH 中会话是否长时间ON CPU抓 Top CPU 的 SQL单个 SQL 变慢执行计划变化看 SQL Monitor Report对比前后执行计划整体变慢且 I/O 等待高存储或 I/O 争抢采集 iostat/AWR区分读/写、文件级 I/O大量会话等待锁应用并发冲突找 blocking_session不要先看磁盘定时出现卡顿备份、归档、定时 JOB对齐时间窗口和后台任务这张表不是我拍脑袋写的全是实践里来来回回踩出来的总结。最怕的就是一个问题明明表现为慢实际却是定时任务造成的瞬时 I/O 尖峰你不看时间线光看全局平均值很容易漏掉真凶。2. 操作系统与 Oracle 两层取证iostat、AWR、动态性能视图怎么对齐I/O 问题排查最忌讳只看数据库层。操作系统看到的是盘的状态数据库看到的是谁来读、读了什么。两层必须对上才能还原完整链路。2.1 系统层指标怎么读才不误判在自建 19c 环境我一般先跑这样一条命令iostat -x 1重点看几列r/s、w/s是每秒读写次数rkB/s、wkB/s是吞吐量avgqu-sz是平均队列长度await是请求从进入到完成的总耗时r_await、w_await分读写来看%util是设备繁忙度。这里有个非常大的坑很多人一看到%util100% 就喊盘满了。实际上%util高只能说明设备在绝大多数时间里被请求占用并不等于物理性能到了天花板。机械盘%util100% 基本可以确认过载但 SSD 和云盘底层是多副本分布式存储%util的计算方式并不能直接反映后端延迟。更值得关注的是await是否稳定有没有经常跳变。云盘限流时通常表现是写延迟突然从 2ms 飙到 50ms而%util反而可能不高。所以以后看到%util高先别急着下结论再看一眼r_await/w_await和avgqu-sz。如果系统里有pidstat还能把 I/O 精确到进程pidstat -d -p 12345 112345 换成 Oracle 某个后台进程的 PID。这样能分清是某个 ARCn 归档进程在疯狂读盘还是用户 SQL 对应的服务进程在大量读数据文件。2.2 数据库层的文件级 I/O比你想的更直观系统层看完回到数据库层。最基础的文件级视图是v$filestat它按数据文件累计了物理读次数、物理写次数、读写耗时。下面的 SQL 可以快速找出平均读延迟最高、读写量最大的文件select f.file#, f.name, fs.phyrds, fs.phywrts, fs.phyblkrd, fs.phyblkwrt, round(fs.readtim / nullif(fs.phyrds, 0) * 10, 2) avg_read_ms, round(fs.writetim / nullif(fs.phywrts, 0) * 10, 2) avg_write_ms from v$datafile f, v$filestat fs where f.file# fs.file# order by fs.phyblkrd fs.phyblkwrt desc;注意v$filestat里的readtim、writetim单位是百分之一秒所以我在 SQL 里乘了 10 换算成毫秒。这个视图是累计值要观察某个时段的变化最好间隔 10 秒跑两次做差值或者直接看 AWR 报告里的表空间物理读统计。如果装了 19c还能用gv$iostat_function按功能归因看 I/O 到底是 Buffer Cache Reads、Direct Reads、DBWR、LGWR 还是 Archive Backup 产生的select file_type_name, function_name, small_read_total_reqs, small_read_total_bytes, large_read_total_reqs, large_read_total_bytes from gv$iostat_function order by small_read_total_reqs large_read_total_reqs desc;这个视图的价值在于它能一眼告诉你归档或备份占了多大比例。很多时候数据文件的物理读并不高反而是归档或备份在读盘光看v$filestat就会错过。2.3 把进程 ID 和 SQL 接起来一段常用的取证 SQL系统层的进程和数据库层的会话可以通过 PID 对上。先查会话对应的操作系统进程select s.inst_id, s.sid, s.serial#, s.sql_id, s.event, s.wait_class, p.spid, p.program from gv$session s left join gv$process p on p.inst_id s.inst_id and p.addr s.paddr where s.username is not null and s.event not like SQL*Net% order by s.inst_id, s.sid;拿到SPID后再去操作系统里用ps -ef | grep spid或pidstat -d -p spid 1确认它的行为。如果这个会话正在db file sequential read而pidstat同时显示它对应 PID 有大量读吞吐那就能确认这个会话确实是物理 I/O 的来源。这一步很重要尤其用于区分用户 SQL 在制造 I/O和后台进程在制造 I/O。我遇到过一种情况应用层几乎没什么用户会话但数据库 I/O 居高不下最后发现是归档进程和自动备份在同时读盘跟用户查询一点关系都没有。3. 云 RDS 场景下的特殊约束没有操作系统时怎么继续查在自建机上你还能登录系统跑strace、perf、blktrace到了云 RDS这些基本别想了。没有 shell没有 root很多参数也改不了。但 RDS 并不是黑盒Oracle 层的东西一个没少v$、gv$、dba_hist能看到AWR 报告通常也能拿云厂商控制台还给了一堆监控曲线。问题只是你怎么把两边的数据对齐。3.1 RDS 上哪些传统手段会失灵在 RDS 环境里第一件事是调整预期iostat、vmstat、sar这类系统命令执行不了即使能跑也是受限版本ALTER SYSTEM很多参数改不了参数变更基本靠参数组日志文件拿不到alert.log往往要通过控制台或 API 获取内核层面更不用想没有 root 权限。但好消息是动态性能视图基本还在。v$session、v$active_session_history、v$archive_dest_status、dba_hist_sysmetric_summary都能查AWR 报告也能通过云平台或 Oracle 自带脚本生成。这就意味着只要你会看数据库层的数据RDS 时代照样能排查无非是少了一个系统层佐证的维度。所以我的建议是在 RDS 环境优先把注意力放到 Oracle 自带的历史指标和 AWR 上不要花太多时间纠结我不能 iostat 怎么办。3.2 用历史指标折线图代替 sar 和 iostatRDS 上查不了系统记录可以用dba_hist_sysmetric_summary看数据库自身统计的历史指标。它包含快照粒度的系统指标按时间拉出来就是一条折线图能比较直观地看到问题时段select instance_number, to_char(begin_time, YYYY-MM-DD HH24:MI) begin_time, metric_name, value, metric_unit from dba_hist_sysmetric_summary where metric_name in ( Physical Read Total IO Requests, Physical Write Total IO Requests, Physical Read Total Bytes, Physical Write Total Bytes, Redo Generated Per Sec ) and begin_time sysdate - 3 order by begin_time, metric_name;不同版本里metric_name的拼写可能有细微差异建议先用desc v$metricname核对一下。比如Physical Read Total IO Requests是累计请求数Redo Generated Per Sec是每秒 redo 生成速率单位不一样别直接横向比较。这些历史指标的价值在于就算你当时不在现场也能事后还原几点几分开始出问题、那个时段 IOPS 和吞吐发生了什么变化。配合 ASH 里对应的采样记录基本能把问题场景复原。3.3 隐藏的 I/O 杀手自动备份、快照、监控采集和克隆RDS 环境里很多 I/O 问题其实不是业务打上去的而是云平台自身的运维操作。最常见的有四个自动备份云 RDS 会在维护窗口做全量或增量备份备份粒度大的时候会读整个数据文件。这个操作通常表现为Physical Read Total Bytes明显上升叠加业务高峰就会出现 I/O 瓶颈。快照有些云平台在做磁盘快照时底层存储会进入一种复制状态配合业务写放大IOPS 监控曲线可能出现短而陡的尖峰。监控采集云厂商自带的采集进程开销很小但如果你自己加了一堆自定义监控脚本反复查v$视图也会产生额外负载。克隆和恢复创建只读实例、从备份克隆新实例时源实例可能被大量读取I/O 也会被推高。排查这类问题最有效的方法是时间对齐。如果 I/O 尖峰每天都发生在固定的凌晨两点先去看那个时间段有没有自动备份任务或维护窗口别急着优化 SQL。4. 归档日志如何成为 I/O 压力的放大器归档和 I/O 的关系很多 DBA 理解得不够深。归档本身不是问题但它在 redo 写入的基础上增加了额外的读和写直接放大了 I/O 压力。4.1 LGWR、ARCn 和归档目录一条完整链路当事务提交时LGWR 负责把 redo buffer 刷到在线 redo log。在线日志写满或触发切换后ARCn 进程会把整个 redo log 文件读出来再写到归档目标。问题就在这里写在线 redo 要消耗写 I/O归档要再读一遍在线 redo、再写一份归档目标。如果归档目标和数据文件在同一块盘上一份 redo 至少产生一次读加一次写这就是 I/O 放大。在云 RDS 里归档通常会异步传到云厂商的远端对象存储本机少了一块写流量但网络传输和归档服务本身又可能成为瓶颈。我用一张表描述这条链路角色/进程在链路里的作用和 I/O 问题的关系LGWR将 redo buffer 写入在线 redo log每次提交都可能有写redo 量大时是写 I/O 压力源ARCn在线日志切换后读取该日志并写入归档目标额外读 额外写形成放大效应归档目录/FRA存放归档日志目录空间不足或性能差会反过来堵住 redo 切换云对象存储/远端归档RDS 上最终的归档去处网络延迟或远端服务异常导致归档跟不上所以在自建环境我一向不建议把归档目录和重数据文件放在同一个物理卷上。哪怕只是换一块独立的云盘I/O 争抢都能缓解一大截。4.2 归档堆积、归档失败与 I/O 的典型关系归档堆积最危险的信号不是归档目录变满而是数据库开始出现下面这类等待事件log file switch (archiving needed)log file switch (checkpoint incomplete)出现第一个说明 ARCn 归档速度跟不上日志切换速度redo 日志组迟迟不能被覆盖重用DML 最终会被卡住。出现第二个说明检查点还没完成日志就切了通常是 DBWR 写能力和 redo 切换速率不匹配。我常用的几个检查 SQL先看在线日志状态select group#, thread#, sequence#, bytes, status, archived from v$log order by thread#, sequence#;再看归档目标状态select dest_name, status, target, destination, error from v$archive_dest_status where status in (VALID, ERROR);看归档量走势select to_char(completion_time, YYYY-MM-DD HH24) hour, thread#, count(*) archives, round(sum(block_size * blocks) / 1024 / 1024, 2) mb from v$archived_log where completion_time sysdate - 7 group by to_char(completion_time, YYYY-MM-DD HH24), thread# order by 1;如果某个小时的归档量突然比平时高出几倍再去对照那个小时的 redo 生成速率和应用行为基本就能锁定是不是有大批量更新或 DDL 操作。4.3 19c 和 RDS 下的归档参数调整与取舍自建 19c 环境里归档相关的常用参数有这么几个log_archive_max_processes控制并行的 ARCH 进程数默认值通常 4可以按需增加到 8 甚至更多archive_lag_target控制最多多少秒触发一次日志切换默认 0 表示不启用db_recovery_file_dest_size如果使用快速恢复区这个大小直接决定归档能放多少。这里最需要当心的是archive_lag_target。有人为了让归档更均匀把值设成 90015 分钟切换一次但对 redo 量本来就大的业务来说频繁切换会产生大量小文件归档消耗额外的 I/O 和 CPU反而适得其反。我一般先看v$log的 redo 组大小再结合每小时 redo 生成量来估算合理的切换频率而不是盲目设个数字。RDS 上很多参数由参数组控制log_archive_max_processes这类参数不一定开放改了也不一定生效。我在 RDS 环境碰到归档跟不上时通常不是去调归档参数而是先看 redo 生成量是不是异常偏高再评估是否存在频繁 commit 或大批量 DML最后才去考虑存储规格和备份窗口。5. 一份可以直接照抄的排查流程和 SQL排查 I/O 问题最怕东一下西一下。我自己沉淀了一套流程每次都是按这个顺序走基本不会漏。5.1 流程总览从 AWR 到 SQL 再到文件三步圈定范围完整流程大概是六步确定问题时间段生成 AWR 报告看 Top Timed Events 和 IO Profile查 ASH找出该时段内等待事件最集中的 SQL 与会话下钻到文件级 I/O定位哪些表空间、数据文件最忙查归档和 redo 相关视图确认归档是不是放大器对 Top SQL 抓执行计划或 SQL Monitor评估访问路径。这套顺序的逻辑是先看全局是哪种等待再看这种等待集中在谁身上最后看它访问了哪些文件、执行了什么 SQL。每一步都能把范围缩小一圈。5.2 当前正在等什么实时会话查询 SQL如果问题还在发生我会先跑一条实时会话查询看看当前大多数活跃会话在哪里select inst_id, sid, serial#, sql_id, sql_child_number, event, wait_class, seconds_in_wait, state, blocking_session from gv$session where wait_class ! Idle and type USER order by seconds_in_wait desc;这条 SQL 能立刻给出一个现场快照哪些会话在等 I/O等了多久是否阻塞。blocking_session列可以帮你快速发现锁等待避免在一堆 I/O 等待里浪费时间。5.3 历史段怎么查ASH、AWR、归档记录一条龙如果问题已经过去就查 ASH 历史采样select inst_id, nvl(sql_id, -) sql_id, event, wait_class, count(*) samples, round(100 * count(*) / sum(count(*)) over (), 1) pct from gv$active_session_history where sample_time systimestamp - interval 30 minute group by inst_id, nvl(sql_id, -), event, wait_class order by samples desc;这个查询会把最近 30 分钟的活动会话按等待事件和 SQL 聚合出来一眼就能看出 Top 等待。要是这段时间没有出现新的报错也可以把sample_time范围改到问题发生的那段时间。找到目标 SQL 后取执行计划select * from table(dbms_xplan.display_cursor(你的sql_id, null, ALLSTATS LAST OUTLINE));如果 SQL 已经被挤出共享池就用 AWR 里的历史计划select * from table(dbms_xplan.display_awr(你的sql_id));注意这些包在普通用户下没有执行权限需要用有DBA权限的账号来跑云 RDS 一般会提供这种权限但具体名字可能不同。5.4 我比较认可的顺序先等、再频、后量看了这么多案例后发现真正高效的思路不是看到等待就处理而是按这个顺序判断先看等待事件它告诉你方向再看请求次数次数高但单次延迟正常多半是 SQL 访问路径问题最后看单次延迟或吞吐量延迟高才是存储侧的问题。举个直观的例子如果db file sequential read平均等待 20ms但次数只有几千重点应该放在存储和链路如果平均等待只有 0.5ms但次数达到几百万这时候存储反而不是主要瓶颈SQL 执行计划才是。很多优化案例最后都发现不是盘不行是 SQL 在访问上实在太低效。6. 实践中踩过的坑和最后的心得排查 I/O 这几年我踩坑无数有些错误换了个环境还是会犯。这里挑几个最有价值的复盘一下。6.1 看到db file sequential read就加索引错这个误区太常见了。db file sequential read高只代表数据库在做大量单块读不代表单块读就很慢。你首先要看平均等待时间平均 1ms 以内磁盘一点问题没有问题在为什么要读这么多次平均 5-10ms机械盘时代算正常SSD 和云盘需要警惕平均 20ms 以上基本可以确认存储或链路有状况。我遇到过一个 19c 实例一张大表查询走了错误索引一次执行物理读 8 万次AWR 里 I/O 等待排第一。有同事第一反应是加索引实际上旧索引选择性太差换个复合索引之后物理读降到 400 次I/O 问题原地消失。所以看到单块读多先看执行计划再看存储延迟顺序不能反。6.2 直接调参数值在 RDS 上行不通在自建库上习惯了alter system set到了 RDS 非常容易踩坑。RDS 的大多数参数由参数组控制甚至有些隐藏参数根本不允许动即使改了也可能在重启后被覆盖。正确做法是先通过v$parameter确认参数当前值和是否可改然后在参数组里做最小变更在维护窗口内生效。更重要的一点是排查阶段尽量别去动参数先定位问题。我见过太多人一上来就把db_file_multiblock_read_count调大把archive_lag_target调小结果问题没解决反而引入了新的 I/O 波动。另外像filesystemio_options、disk_asynch_io这类和操作系统直接相关的参数在云 RDS 上基本处于不可控状态别指望通过调它来优化。真要提升 I/O 能力更实际的路是扩大存储 IOPS、优化 SQL、错开备份窗口。6.3 几个我觉得值得长期保留的排查习惯最后分享几个我一直在用的习惯。每季度给核心实例做一次 I/O 基线采样记录正常时段的物理读/写 IOPS、吞吐、平均读延迟/写延迟、redo 生成速率和归档量峰值。有了基线看到监控曲线异常时就能第一时间判断是偏离还是正常波动。排查问题时尽量保留原始材料问题时间段的 AWR 报告、ASH 采样、Top SQL 执行计划哪怕当时没用上事后复盘也经常能发现蛛丝马迹。处理问题要有耐心从应用层、数据库层、OS 层、存储层逐层排查每层都要有量化证据不要跳过。I/O 问题说到最后不是找到一个坏组件而是解释清楚为什么这一小时突然多了这么多读写。排查得多了你会发现所谓 I/O 问题大部分时候不是磁盘一个环节的事而是一整条读写链路里若干个同时发生的巧合。把每个环节的指标摆出来再谈优化那些看似吓人的等待事件自然就藏不住了。
返回列表