最近有个刚转DBA的朋友问我一个问题业务反馈数据库卡顿他第一件事就是翻alert日志结果翻了大半天连个ORA-报错都没看到彻底不知道下一步该干嘛了。这个场景我太熟悉了——排查卡顿相关日志核心压根不是“找报错”而是沿着时间线把从应用请求、数据库会话、SQL执行到系统资源消耗的整条链路逐层还原。alert日志只是其中一环甚至很多时候不是最关键的一环。这篇我按普通DBA排查Oracle卡顿的正常顺序来写先定义问题方向再翻日常日志然后做AWR/ASH时间线还原之后上升到SQL级追踪和锁定位最后附一张高频场景速查表。内容尽量贴近实际操作该给的SQL和命令都直接给你看完照着做就行。1. 卡顿排查的定位方法论先把方向走对1.1 定义问题边界卡顿不等于数据库卡“卡顿”这个词太抽象了不同角色说“卡”的时候指的是完全不同的东西。业务说“数据库卡”可能只是某张报表的查询转圈应用开发说“卡”可能是一条update语句迟迟不返回而领导说“系统卡”往往意味着整个交易链路响应都变慢了。所以第一步永远是问清楚是全库卡还是个别SQL卡是平时快、特定时段才卡还是一直都卡是CPU飙高了还是内存吃紧还是IO打满这些信息决定你接下来往哪个方向查。我个人习惯先把问题归为三类CPU类数据库的CPU消耗异常高负载常年压在90%以上。IO类磁盘读写繁忙物理读大存储层响应慢。等待类SQL本身不慢但大量会话在争抢锁、日志缓冲或内部latch。这三类问题的日志侧重点完全不同。CPU问题要先看系统和AWR的DB CPU占比IO问题要盯磁盘指标和db file类的等待事件等待类问题则要深入v$session和锁视图。方向定错了日志翻得再多也只是自我安慰。1.2 三种卡顿现象与日志路径对照我把常见的现象、日志入口和需要重点关注的指标列成了一张对照表排查的时候直接对号入座现象表现系统侧证据数据库侧关键日志与报告主要等待/指标优先排查工具整库响应极慢CPU持续高位vmstat中r列远大于CPU核数mpstat单核跑满AWR的DB Time分布ASH中ON CPU占比过高DB CPU、CPU used by this sessionAWR报告、ASH采样、v$sql业务侧超时但数据库CPU不高iostat await接近或超过20ms磁盘util接近饱和alert无异常AWR中IO相关等待突出db file sequential read、db file scattered readOSWatcher、iostat、AWR IO Profile某个会话或任务长时间不结束系统负载正常CPU、IO都平稳ASH显示大量锁等待alert偶发ORA-00060enq: TX - row lock contention、enq: TM - contentionv$session、v$locked_object连接建立极慢应用频繁报连接池耗尽网络抓包有重传ping延迟不稳listener日志显示accept后响应慢SQL*Net break/reset to client次数多listener日志、sqlnet trace这个表不是标准答案而是我平时排查的默认动作序列。它不是靠一个日志就拍板的要几个证据互相印证。1.3 动手前先固化三个凭证第一卡顿发生的时间窗。要精确到分钟最好是能切到秒。第二业务现象是查询慢、更新卡住、还是连接不上。第三这个时间段是否发生过变更比如发布新版本、调整过参数、导入过大量数据、跑过统计信息采集。这三件事缺一不可。你后面翻alert日志要指定时间段查ASH要用时间窗过滤对比AWR要看这期间的快照没有时间窗等于大海捞针。至于变更记录很多时候数据库本身没问题就是某个应用发布包改了一条SQL的绑定逻辑结果执行计划变异卡顿就来了。这个信息如果到了排查中后期才拿到会白白浪费几个小时。2. Alert与Listener日志最先该翻的日常证据2.1 alert日志里真正需要警惕的记录alert日志在$ORACLE_BASE/diag/rdbms/{dbname}/{SID}/trace/alert_{SID}.log它记录的是实例级的重大事件比如启动关闭、日志切换、内部错误、死锁检测等。很多DBA习惯整段翻这不对信息太杂。正确姿势是用时间窗过滤再用特征词过滤grep -n 2025-01-10 alert_X.log | grep -E ORA-[0-9]{4,5} | head -50如果时间段内出现了下面这些记录直接就能圈定方向ORA-00600 / ORA-07445内部错误通常伴随进程异常需要收集trace文件进一步分析。ORA-00060检测到死锁。少量死锁不用慌但alert日志里连续出现说明应用侧事务设计有问题。ORA-01555snapshot too old一般是UNDO空间或UNDO保留策略没跟上查询时长。ORA-01652 / ORA-01653表空间或临时表空间无法扩展这种情况业务直接报“卡住”因为所有需要分配空间的SQL都在等待。光看alert日志只能定位“数据库有没有明显故障”它不会记录慢SQL不会告诉你某个等待事件持续了多久。所以alert日志翻开之后如果没发现异常别回头又从头翻一遍该转方向了。2.2 连接层卡顿排查listener日志与网络追踪如果业务反馈的是“连接不上”“连接超时”而不是“SQL执行慢”那就去翻listener日志位置在$ORACLE_BASE/diag/tnslsnr/{hostname}/{listener}/alert/log.xml里面记录每次连接的发起时间、客户端IP、服务名和结果。连接建立阶段卡顿最典型的特征是log.xml里同一个客户端IP在同一秒发起大量连接或者从收到请求到完成连接响应的时间明显变长。这种情况下我一般同时做两件事第一用tcpdump或系统抓包看网络层有没有丢包重传第二临时在客户端的sqlnet.ora里开一层追踪TRACE_LEVEL_CLIENT SUPPORT TRACE_FILE_CLIENT sqlnet_trace开这个trace会生成客户端侧的连接协商记录能看到DNS解析、网络地址解析、协议握手每一步的耗时。生产环境慎开这个trace增长非常快最多开几分钟就得关掉。实测下来连接慢很多时候不是Oracle的锅是防火墙策略、DNS解析超时或者中间件连接池配置问题。要记住listener日志只能告诉你连接有没有建立成功但它不能告诉你网络中间发生了什么。2.3 系统侧的日志与工具把CPU、内存、IO证据补全数据库是跑在操作系统上的排查卡顿不看系统日志就是盲人摸象。最简单的起步命令sar -u -r -b -q -f /var/log/sa/sa10Linux系统都有sar历史数据sa10表示当月的10号。-u看CPU-r看内存-b看IO-q看负载队列。把这四个维度的曲线拉出来和业务反馈的卡顿时间点对上系统层和数据库层到底谁先异常一眼就能分出来。比如19:30开始CPU冲高而数据库ASH里这个时间点也正好出现大量ON CPU会话那卡顿源头大概率就是SQL计算量大而不是磁盘慢。另外Oracle自己的OSWatcher采集工具也很好用。它是一组shell脚本会周期性采集vmstat、iostat、top、内存等数据归档成文本和图表文件。但这个东西需要提前部署没装的话就别指望了用系统自带sar配合iostat也能完成同样的现场还原。补系统侧证据时有个坑要特别提醒检查数据库服务器和应用服务器的时间是否同步。时间漂移会导致两个系统的日志对不上甚至会把卡顿起点定位偏几个小时。3. 锁定卡顿窗口AWR/ASH时间线还原法3.1 AWR与ASH趋势报表和秒级采样怎么分工AWR默认每小时生成一次快照保留7天。它像一张体检报告适合看整体趋势和对比基线。ASH则是按秒采样活跃会话保留了会话当时在执行的SQL、等待事件和运行状态。一次卡顿可能只持续十分钟AWR那个小时级别报告会把卡顿前后的正常数据全混在一起反而稀释了问题。所以我的习惯是AWR确定大方向ASH还原真实现场。先查卡顿时间窗落在哪些快照SELECT snap_id, begin_interval_time, end_interval_time FROM dba_hist_snapshot WHERE begin_interval_time BETWEEN TO_DATE(2025-01-10 20:00:00,YYYY-MM-DD HH24:MI:SS) AND TO_DATE(2025-01-10 22:00:00,YYYY-MM-DD HH24:MI:SS) ORDER BY snap_id;然后针对这1~2个快照生成AWR报告。如果卡顿只持续了15分钟光看快照之间的平均值没意义必须用ASH把15分钟内每秒发生了什么拉出来。反过来如果ASH显示某个SQL大量消费资源也可以用AWR确认它在整个小时内占了多大的DB Time比例。两者结合定位才不会有偏差。3.2 卡顿窗口内的等待事件先看等待再查SQL我排查任何卡顿都会先问一个问题会话状态是WAITING还是ON CPU这个答案直接把问题分成两类——等资源还是耗CPU。v$active_session_history会记录session_state查询卡顿窗口内Top等待事件SELECT session_state, event, wait_class, COUNT(*) AS sample_cnt FROM v$active_session_history WHERE sample_time BETWEEN TO_TIMESTAMP(2025-01-10 21:00:00,YYYY-MM-DD HH24:MI:SS) AND TO_TIMESTAMP(2025-01-10 21:30:00,YYYY-MM-DD HH24:MI:SS) GROUP BY session_state, event, wait_class ORDER BY sample_cnt DESC;结果出来后重点分析这些典型的等待事件db file sequential read单块读通常是索引访问加回表SQL执行计划大概率做了大量随机读。db file scattered read多块读典型全表扫描或索引快速全扫描。表巨大且频繁出现这个等待就要考虑SQL写法、分区裁剪或者查询条件是否真正用到索引。enq: TX - row lock contention行锁竞争两个会话在抢同一行数据典型的锁等待场景。log file sync会话等待日志写入完成通常跟磁盘上redo存放位置的IO能力有关也可能跟应用频繁小事务提交有关。看到等待事件后再去定位具体SQL。通常我是按采样数倒序找哪个SQL在这个窗口里sample次数最多就是消耗最猛的那个。之前见过太多同事一上来就查v$sql寻找执行时间最长的SQL这种思路在持续几个小时的大SQL上有效但在突发卡顿场景里会漏掉真正的元凶。必须以时间窗为中心先看这个窗口谁最活跃。3.3 从Top SQL到执行计划提炼有分析价值的SQL定位到SQL_ID之后先到v$sql里看它的执行特征SELECT sql_id, executions, buffer_gets, disk_reads, elapsed_time, cpu_time, rows_processed FROM v$sql WHERE sql_id XXXXXXXX;这里要分两种场景看数据。一种情况是buffer_gets很高但disk_reads很低说明SQL在buffer cache里大量扫块执行计划走了低效路径重点去查执行计划为什么没走索引或者为什么嵌套循环扫了海量数据。另一种情况是disk_reads很高尤其伴随着db file sequential read或scattered read说明物理IO吃紧要结合存储端看具体数据文件。查看执行计划最简单的方式是SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR(SQL_ID, NULL, NULL, ALL));盯着这几个点访问路径是否合理索引范围扫描还是全表扫描、预计行数和实际行数差距大不大、是否存在笛卡尔连接或嵌套循环放大。实际行数与预计行数偏差太大几乎所有卡顿SQL最后都能归结到这个根因上。4. 外科手术级追踪SQL Trace与阻塞源定位4.1 10046事件给指定会话装上记录仪AWR和ASH解决的是“哪些SQL有问题”但它们看不到SQL内部的执行细节比如每条SQL在执行阶段、获取阶段到底花了多久具体在等什么。这时候就用SQL Trace也就是常说的10046事件。最常用的Level是12它同时记录bind变量和等待事件信息最全。如果要追踪一个正在运行的会话用DBMS_MONITOR打包好的存储过程BEGIN DBMS_MONITOR.SESSION_TRACE_ENABLE( session_id 1234, serial_num 5678, waits TRUE, binds TRUE ); END; /这个方式比直接ALTER SESSION SET EVENTS更容易控制而且不用去记事件编号。等业务复现完问题立刻关掉BEGIN DBMS_MONITOR.SESSION_TRACE_DISABLE(session_id 1234, serial_num 5678); END; /trace文件会写到数据库的trace目录SELECT value FROM v$diag_info WHERE name Diag Trace;注意生产环境开trace要谨慎它对性能有损耗而且在活跃会话上开tracetrace文件可能几分钟就写几百MB。最好的方式是挑业务低峰或者能让业务复现问题的那几秒开问题一复现就关。4.2 tkprof输出怎么读重点盯执行与等待裸的trace文件非常难读全靠tkprof格式化tkprof /u01/app/oracle/diag/rdbms/xdb/xdb/trace/xdb_ora_1234.trc /tmp/tkprof_out.txt sortexeela sysno aggregateyessortexeela表示按执行总耗时排序sysno把系统递归SQL过滤掉。打开tkprof输出重点看三段信息Parse解析阶段耗时、Execute执行阶段耗时、Fetch取数据阶段耗时。对OLTP系统来说最值钱的是Execute和Fetch耗时偶尔出现某个SQL在Parse阶段耗时几秒钟那就要怀疑高版本硬解析太多或者共享池有压力。每个SQL条目下面跟着一个Wait Events小节这里记录了那次执行过程中每个等待事件的发生次数、等待总耗时。如果看到某个SQL的Elapsed Time明明很高但CPU Time很低那意味着大量时间都花在等待上——要么是IO等待要么是锁等待要么是对端应用没来取数据。4.3 阻塞源头定位锁视图组合查询实战锁等待是卡顿的高发区也是最容易让新手绕圈子的场景。查锁不要一上来就看v$lock里那一堆type直接用v$locked_object配合v$session组合查询能直接得到“哪个对象被锁、谁在等、谁在阻塞”SELECT o.object_name, o.object_type, lk.session_id AS sid, s.username, s.machine, s.event, s.wait_class, s.sql_id FROM v$locked_object lk JOIN dba_objects o ON lk.object_id o.object_id JOIN v$session s ON lk.session_id s.sid ORDER BY o.object_name;再查谁是阻塞源头SELECT sid, serial#, username, blocking_session, event, wait_class, seconds_in_wait, sql_id FROM v$session WHERE blocking_session IS NOT NULL AND wait_class Idle;blocking_session就是阻塞当前会话的那个会话SID。看到它之后再去v$session里查这个阻塞会话在干什么、跑什么SQL、事务开了多久。我遇到过一个典型的虚构案例某业务系统X的夜间批量导入任务两个会话分别持有两张表的行锁然后各自去更新对方持有的行互相等alert日志里偶尔蹦出ORA-00060。表面看是死锁实际是应用层更新顺序不一致底层业务逻辑设计就有问题。这种问题日志只是表象源头在应用事务设计。5. 常见问题速查表与实战避坑5.1 高频卡顿场景与排查对照表我把这几年高频遇到的卡顿场景整理成了一个速查表排查时可以先快速对号入座卡顿表现定位入口关键日志/视图常见解法方向整库CPU居高不下业务普遍慢ASH中ON CPU会话占比高等待事件几乎为空session_state都是ON CPU抓Top SQL优化逻辑读排查执行计划突变单个查询突然变慢其他功能正常v$sql中该SQL的buffer_gets突增查看DBA_HIST_SQLSTAT中plan_hash_value变化从历史执行计划中对比必要时候绑定执行计划更新语句找不到记录但一直被阻塞v$session中wait_classApplication/Concurrencyenq: TX - row lock contention查blocking_session找未提交事务推应用修改事务逻辑应用连不上库但数据库负载很低listener日志中SYN到accept耗时长log.xml大量尝试连接记录查网络抓包、防火墙策略、连接池配置日志切换频繁写库缓慢alert日志中 Thread advanced to log sequencelog file sync等待突出增大redo log组大小检查redo文件所在磁盘IO能力临时表空间不断增长偶尔报ORA-01652alert日志或临时表空间使用率监控大量排序和hash join操作优化排序类SQL调整PGA或临时表空间大小夜间批处理报表互相卡死ASH时间窗内大量enq等待enq: SQ - contention / enq: TX检查sequence缓存参数和事务隔离级别需要说明的是这张表只是指个方向。每个场景最终落到具体数据库实例上还要结合当时的时间窗、负载水平、变更记录综合判断没有哪一条是绝对的金科玉律。5.2 几条实测下来最重要的工作习惯先看系统再开数据库这是原则。时间同步、负载曲线、变更记录这些信息一定提前备份下来。我见过有人在现场查了三个小时数据库日志最后发现是DDL操作导致共享池的游标全部失效大量会话在硬解析。如果一开始就看时间点附近有没有DDL这个问题十分钟就能确认。多做基线对比。每一次排查都要拉历史同时间段的数据做对比比如同一个SQL昨天的执行计划、上周的buffer gets、上个月同一天的系统负载。没有对比你就不知道当前数字是正常波动还是明显异常。参数调整必须有证据。有人一看到卡顿就调sga_target、pga_aggregate_target这是最典型的盲目操作。内存参数调大可能短期掩盖问题但第二天换个SQL执行计划突变又卡了。除非AWR里明确显示Buffer Cache或PGA命中率异常否则不要去动这些基础参数。全文日志别乱清。排查过程中凡是涉及到的日志和trace文件第一时间备份一份到独立目录。前车之鉴有一次我确认了卡顿原因后顺手把listener日志清了结果后续复盘时缺少了原始证据分析报告写得很被动。我个人实际操作中最深刻的体会是排查卡顿相关日志越往后越少依赖单一日志更多依赖多份日志交叉印证。alert日志给出的是异常信号AWR/ASH还原的是时间线listener和系统日志补齐的是链路两侧的细节锁视图和SQL Trace则负责坐实最终结论。这套流程只要走顺了大多数卡顿问题都能在半小时内圈定一个大方向。