“系统又卡了”这句话,几乎是每个Oracle DBA的午夜梦魇。接手一个“oracle排查卡顿相关日志”的工单,最怕的不是问题复杂,而是面对一堆毫无头绪的日志不知道从哪里下手。这篇内容就是写给正在跟卡顿问题搏斗的运维、开发和架构同学的,我会从真实的排查视角出发,讲清楚告警日志、监听日志、系统统计信息和操作系统日志各自的定位,以及一套可以直接落地复用的分析顺序。读完你会发现,卡顿排查其实是有章法的。
1. 先别急着查日志,先把“卡顿”的定义说清楚
接到报障电话,我建议你第一句话先别问“日志在哪”,而是耐着性子问清楚:是所有业务都卡,还是某一个页面/某一条SQL卡?是一直卡,还是每隔一段时间卡几分钟?这三个问题决定了你后续排查完全不同的方向。
我自己踩过不少弯路。有一回某业务方半夜打电话说“库里锁死了”,我冲进机房一路查v$lock、杀会话,忙活半小时,最后发现人家说的“卡”只是他们自己应用网关超时,数据库压根没收到几个请求。所以拿到“卡顿”两个字,先别急着当技术问题处理,它可能只是应用侧感知异常,也可能真的来自数据库内部争用。
从技术角度看,Oracle里的卡顿通常能拆成三类:
| 卡顿类型 | 典型表现 | 大概率相关模块 |
|---|---|---|
| 全库型卡顿 | 所有应用、所有会话都变慢 | 日志切换、归档堵塞、共享池问题、I/O子系统故障 |
| 单会话型卡顿 | 只有某个模块或某条SQL慢 | SQL执行计划劣化、行锁/表锁争用、绑定变量窥探 |
| 周期性尖刺 | 每隔固定时间抖一下 | Job调度集中、日志切换周期、统计信息自动收集、检查点 |
把这个分类记在脑子里,再看日志就有方向了。日志的作用从来不是第一排查手段,而是用来验证你的猜测、补齐时间线的证据。
另外要特别提醒:Oracle官方给出的诊断路径里,数据库的告警日志只是入口,真正要把“为什么慢”钉死,必须结合动态性能视图(v$视图)、AWR/ASH报告和操作系统层的证据链。这也是后面几章我要展开的内容。
2. 告警日志:数据库主动留下的“案发现场”
2.1 告警日志在哪,翻天覆地找不到不如用adrci
数据库的alert日志(告警日志)就像服务器的“黑匣子”,数据库内部发生的严重错误、内部错误、死锁、表空间相关报错都会记在这里。从11g之后,告警日志被纳入ADR(Automatic Diagnostic Repository)体系,路径通常是:
$ORACLE_BASE/diag/rdbms/{db_unique_name}/{SID}/trace/alert_{SID}.log如果你不想一层层翻目录,直接在命令行用adrci最省事:
adrci> show alert -tail 200加-tail是只看末尾200行,适合快速确认最近有没有新错误。如果数据库还活着但卡得不行,先扫一遍alert日志里有没有ORA-00600、ORA-07445这种内部错误;如果数据库根本连不上,那alert日志最后几行往往就写着数据库为什么起不来。
2.2 日志切换失败导致的“全库卡死”识别
我遇到过的全库卡顿,有相当比例根因是归档日志写不进去。alert日志里反复出现这类信息:
ORA-00313: unable to open archived log '/u01/arch/xxx_12345.arc' ORA-00312: online log 1 thread 1: '/u01/oradata/redo01.log'这种情况下,所有需要提交的事务都会卡在日志写入环节。你去看会话等待,会看到大量log file switch (archiving needed)或archiver stuck,整个库就像被按了暂停键。
排查顺序是这样:先确认归档目录空间是否打满df -h,再看归档进程状态archive log list,最后在v$archive_dest_status里看归档目标是不是失效了。如果归档盘满了,把空间清理出来,数据库通常几秒内就能恢复“呼吸”。
2.3 别忽略checkpoint not complete这类“温柔”的停顿
还有一种卡顿不像ORA-00313那样报错明显,而是alert日志里反复刷这样的信息:
Thread 1 cannot allocate new log, sequence 12345 Checkpoint not complete这意味着日志切换时,DBWR还没把脏块写完,LGWR只能原地等待。表现就是:每隔几分钟,数据库准时抖一次,每次抖几秒到几十秒,业务时好时坏。
针对这种问题,光看alert日志不够,还要配合两个视图:
-- 查看日志切换频率与大小 select to_char(first_time, 'yyyy-mm-dd hh24:mi') day, count(*) cnt, round(sum(blocks*block_size)/1024/1024) mb from v$log_history group by to_char(first_time, 'yyyy-mm-dd hh24:mi') order by 1 desc; -- 查看DBWR是否频繁等待 select event, total_waits, time_waited/100 time_sec from v$system_event where event like '%checkpoint%' or event like '%log file parallel write%';如果redo日志切换非常频繁(比如每3到5分钟就切一次),通常是redo日志组太小、业务DML过于密集,或者频繁commit但没有批量提交。针对这种场景,加大redo日志文件、调整checkpoint时机,往往比反复优化SQL更见效。
2.4 死锁记录ORA-00060:日志里的“业务冲突实锤”
如果卡顿伴随“某个操作报ORA-00060”,alert日志里会留下完整死锁图:
ORA-00060: Deadlock detected. More info in file /u01/diag/.../trace/xxxx_ora_1234.trc跟踪文件里会详细列出两个会话各自持有的锁、等待的锁、涉及的SQL语句。我见过很多所谓的“数据库卡死了”,本质是应用层两个模块更新同一张表的顺序不一致:A先update表1再update表2,B先update表2再update表1,高并发下交叉等待,直接形成环形等待。
这类问题的修复通常不在数据库侧,而是在业务侧统一加锁顺序,或者走事务队列。作为DBA,你能做的就是把死锁跟踪文件里锁对象、SQL文本拉出来,交给开发优化逻辑顺序。
3. 监听日志与跟踪文件:连接异常的另一条线索链
3.1 连不上库、连接极慢,盯紧listener.log
很多卡顿其实发生在“连接建立”这个环节,而不是SQL执行环节。客户端一发起连接就卡住,或是应用报ORA-12520、ORA-12516,这种时候要看监听日志:
$ORACLE_BASE/diag/tnslsnr/{hostname}/listener/trace/listener.log监听日志里每个连接请求都会记录客户端IP、端口、请求的Service Name、处理结果。如果短时间涌入大量请求,日志会哗啦啦地刷;如果进程数被占满,就会看到TNS-12520: TNS:listener could not find available handler。
判断连接慢的逻辑:如果连接请求之间时间戳间隔正常,只是单次建立连接耗时很久,问题通常在网络链路或DNS解析;如果同一时刻连接请求堆积成山,多半是应用连接池配置过小、被夯住了。
有个很实用的检查动作:在客户端机器上执行
tnsping 数据库服务名如果tnsping每次往返都超过几十毫秒且不稳定,那基本可以甩锅给网络了,数据库侧日志再翻也翻不出花来。Windows平台还可以用sqlplus username/password@tns别名观察“连接到服务器”和“已连接”之间的耗时,粗略定位是哪一段慢。
3.2 用10046跟踪文件把一个会话“解剖”开
有时问题不在连接,而在某个具体会话内部。比如业务反馈“点保存按钮要转十秒”,但AWR里看不出所以然,因为单条SQL本身并不慢。这时最好的工具是SQL trace(10046事件)。
alter session set max_dump_file_size = unlimited; alter session set events '10046 trace name context forever, level 12'; -- 执行让业务复现一段慢操作 alter session set events '10046 trace name context off';生成的trace文件会记录每个操作的等待事件、CPU消耗、物理读、逻辑读、解析时间。我处理过一个“报表导出卡顿”案例,SQL执行计划完全正常,但trace文件里全是log file sync等待,一查代码,原来是导出一行数据就commit一次,几千行数据提交了几千次,大量时间浪费在日志同步上。
掌握这个工具之后,“数据库卡顿”就能被拆成“解析慢、执行慢、等待资源慢”三种,定位精度完全不一样。
4. 日志之外,真正的高频排查路径是等待事件
4.1 卡顿当下抓现场:v$session和ASH是比日志更快的第一现场
如果卡顿正在发生,别等日志慢慢刷,直接执行下面这条SQL就能看到全场会话的等待状态:
select sid, sql_id, event, blocking_session, seconds_in_wait, wait_class, module from v$session where wait_class != 'Idle' and status = 'ACTIVE' order by seconds_in_wait desc;重点看两个字段:EVENT是当前会话在等什么,BLOCKING_SESSION是被谁堵住了。如果BLOCKING_SESSION非空,顺着这个会话往上追,就能找到锁的源头。
如果卡顿是几分钟前发生的,你已经错过了当下现场,那就查ASH(Active Session History):
select to_char(sample_time, 'hh24:mi') sample_minute, event, count(*) cnt from v$active_session_history where sample_time > sysdate - 1/24 group by to_char(sample_time, 'hh24:mi'), event order by 1;ASH是数据库自动保留的会话级采样历史,相当于把过去一段时间每个会话“在做什么”记录了下来。哪怕是事后排查,也能还原出卡顿那一刻所有会话的聚合行为。
4.2 I/O类等待:卡顿的“重灾区”
从等待事件分类来看,最容易造成严重卡顿的几类等待有:
| 等待事件 | 含义 | 常见根因 |
|---|---|---|
| db file sequential read | 单块随机读慢 | 存储I/O延迟高、冷热数据分布差、索引失效 |
| db file scattered read | 多块全表扫描 | 缺索引、坏计划、疯狂全表扫 |
| direct path read/write | 直接路径读写 | 并行度太高、临时表空间压力大 |
| log file sync | redo落盘慢 | 频繁提交、存储写入慢、redo日志争用 |
| enq: TX - row lock contention | 行锁争用 | 高并发更新同一行、未提交事务锁表 |
| library cache lock | 共享池游标争用 | 硬解析风暴、存储过程失效重编译 |
怎么判断是不是存储问题?看平均等待时间。比如db file sequential read总等待时间很长、平均单次等待超过十几毫秒,就要怀疑存储链路;如果只是某一条SQL引起的等待次数很多,平均等待本身不高,那问题更可能在SQL设计。
我排过的一个典型场景:某生产库每天下午三点整点卡顿,等待事件集中在direct path write temp,顺着时间线一查,正好赶上某报表Job并行度设置为8的排序操作,海量临时数据涌向临时表空间,把I/O打满。把并行度调小,问题立刻消停。这种问题,你在alert日志里永远看不到,但等待事件会告诉你一切。
4.3 锁等待:从日志到阻塞链路的“顺藤摸瓜”
库卡住,最常见的另一个元凶是行锁、表锁或系统锁等待。查当前阻塞链路比较实用的SQL是:
select sid, blocking_session, event, wait_class from v$session where blocking_session is not null order by sid;假设查出来SID 100被SID 88阻塞,而88本身又在等待另一个会话,那就是一条阻塞链。继续递归下去,直到最后那个event不是锁等待、而是SQL*Net message from client的会话,那就是一个“打开事务但什么都没干,手里攥着锁不放”的应用端未提交事务。把这个会话信息甩给业务开发,让他们检查代码里是不是忘了commit或者事务超时设太长。
别指望alert日志能帮你揪出这种锁问题,它只会偶尔打印一条ORA-00060。真正的现场在v$lock、v$session和DBA_BLOCKERS这些视图里。
5. 跨界排查:OS、存储和网络日志的配合使用
5.1 数据库日志正常,不代表系统没病:操作系统日志不能少
有一个特别容易进死胡同的场景:alert日志干干净净,AWR报告看起来也还行,但系统就是慢。这时候我强烈建议你把排查范围扩大到数据库之外。
先看操作系统层的三项指标:
# CPU与负载 sar -u 1 5 # 内存与换页 sar -r 1 5 # I/O设备的svctm、await、util sar -d 1 5%wa过高,说明CPU在等待I/O,对应的是存储慢;pgpgin/pgpgout频繁大数值,说明内存不足在疯狂换页;util%长期接近100%,说明存储盘的性能已经到极限。这些证据比任何AWR里的“耗时”都更接近物理真相。
再比如alert日志里出现ORA-00600 [17182],很多DBA第一反应是Bug,但我在OS的/var/log/messages里看到的是OOM killer直接干掉了数据库进程。OS没内存了,把oracle进程杀了,数据库内部再排查也不解决根本问题。这就是“数据库日志正常,操作系统日志要命”的典型例子。
5.2 别让网络背锅,但更不能忽略网络丢包
数据库会话等待SQL*Net message from client时,很多新手会把这条当成问题,其实这是个“伪等待”。它只是会话在等客户端发送下一条指令,数据库这边什么事都没干。真正的风险在于:如果客户端在跨机房的链路上经常丢包、重传,一次普通的SQL请求在网络上反复重传,用户感知到的就是“数据库卡了”。
这种情况下数据库侧的所有日志都不会有异常,你必须到操作系统的网络层找证据:
# 查看网卡丢包、错误统计(注意看drop与errors列) netstat -i # 或者在客户端与服务端之间抓包统计TCP重传 tcpdump -i eth0 tcp port 1521 -w /tmp/client.pcap抓到包之后用wireshark打开,重点看TCP Delta和重传标记。如果一段正常的SQL查询往返时间在数据库内只有5毫秒,而TCP层却耗时200毫秒,那结论很清晰:链路质量背锅,别再把开发叫过来改SQL了。
5.3 存储日志:数据库看到的是表象,存储报的是实底
数据库等待db file sequential read平均要几十毫秒,几乎可以断定不是数据库自身问题。这时候建议登录存储管理端,看物理磁盘的健康状态和延迟指标。
我遇到过一个“诡异卡顿”:数据库AWR显示I/O等待很高,但OS的iostat延迟正常。后来存储工程师在阵列日志里发现某块磁盘的扇区重读次数暴涨,触发了磁盘降级,阵列内部在做重建,数据访问全部被拖慢。这种事在数据库日志里看不到,在OS层面也可能被阵列缓存掩盖,只有存储日志才能扫出来。
所以跨界排查的次序,我建议是:先数据库内、再数据库外,但最终一定要走到OS和存储日志。很多折腾一夜的卡顿,最后发现就是底层硬件或网络链路的问题。
6. 我自己常用的卡顿排查顺序与几个实操建议
6.1 一张可以直接抄走的检查清单
排查卡顿类问题,我给自己定了一个固定套路,现在分享出来:
| 步骤 | 检查内容 | 关键命令/视图 | 判断目标 |
|---|---|---|---|
| 1. 确认范围 | 报警描述、影响模块、持续时间 | 问业务人员 | 判定全库型还是单会话型 |
| 2. 看alert日志 | 最近错误、死锁、日志切换问题 | adrci show alert -tail | 排除内部错误、归档、OOM |
| 3. 抓当下状态 | 当前活动的会话与等待事件 | v$session、v$sqlarea | 锁定等待事件与阻塞链 |
| 4. 回溯时间轴 | 卡顿时刻ASH/DBA_HIST_ACTIVE_SESS_HISTORY | v$active_session_history | 定位具体SQL与对象 |
| 5. 对照AWR/ADDM | 数据库自身负载画像 | awrrpt.sql、addmrpt.sql | 看TOP事件、TOP SQL、Top Objects |
| 6. 查OS日志与指标 | CPU/I/O/内存/网络 | sar、iostat、netstat、/var/log/messages | 排除数据库外因素 |
| 7. 结合存储日志 | 磁盘健康、延迟、控制器 | 存储在对应管理界面 | 排除底层故障 |
这套顺序的核心逻辑,是从“数据库告诉我的”逐步外推到“数据库告诉我的内幕”。一定不要把时间花在逐条翻日志上,要先把时间轴建立起来。
6.2 平时把“弹药”备足,卡顿时才不吃力
说句实在话,排查卡顿最怕的不是没日志,而是需要历史数据的时候发现没采集过。有几个基础习惯我建议你提前养成:
- 确认AWR和ASH采集是开启状态,
statistics_level至少是TYPICAL,AWR自动快照间隔建议30分钟或更短; - 给alert日志和监听日志所在的文件系统设置独立的告警阈值,磁盘满之前就会提前收到风声;
- 遇到重大卡顿,第一时间手动保存一个AWR快照对和ASH dump,这才是卡顿发生后最珍贵的现场数据;
- 至少每月做一次redo切换频率和归档空间趋势检查,很多周期性的“卡一下”都能从趋势里提早发现。
6.3 最后一个经验:把“卡顿”当刑事案件办,别急着下结论
排查无数次卡顿之后,我的体会是:最贵的教训来自“先入为主”。一看等待事件是I/O就认定存储慢,结果存储那边查了三天,最后发现是某张表被全表扫描了千万行;一看SQL耗时就改SQL,结果发现是网络重传让一条毫秒级SQL来回跑了几十趟。
正确的动作是:先取证,再定案。把业务感知、数据库内部证据、OS证据、网络与存储证据全部摆到一张时间轴上,找到那个唯一能解释所有现象的点。日志是“案发现场”,等待事件是“目击证人”,操作系统日志和存储日志是“法医报告”,每个角色都不能少。
最后说句掏心窝子的话:排查Oracle卡顿相关日志这件事,最终拼的不是你对某一条命令多熟,而是你有没有一套能反复验证的时间线思维。先确认“卡”在哪一层,再去翻对应的日志,你的排查效率至少能提高一倍。希望这份清单能让你少走几次我走过的弯路。