1. 项目概述一次典型的Oracle数据库Hang进程排查实录那天下午监控告警突然响了提示某个核心业务数据库的响应时间飙升。登录服务器一看一个关键的批处理作业已经“卡”在那里超过半小时前端应用连带着一片超时。经验告诉我这大概率是遇到了Oracle数据库中经典的“Hang”挂起进程问题。对于DBA和运维工程师来说处理Hang进程就像急诊医生处理急症需要快速定位病因、果断处置同时还要避免“误伤”正常业务。这次遇到的案例正是一个由资源争用引发的典型Hang进程最终通过移除Kill该会话得以解决。但“移除”二字背后远不止一句ALTER SYSTEM KILL SESSION那么简单它涉及对Oracle等待事件、锁机制、系统视图的深度解读以及一套严谨的排查与操作流程。如果你也经常需要与Oracle数据库的“疑难杂症”打交道那么这次完整的排查思路和实操记录或许能为你提供一个清晰的参考模板。2. Hang进程的本质与常见诱因分析2.1 什么是Hang进程它和慢查询有何不同首先必须厘清一个概念Hang进程挂起进程不等于慢查询。慢查询是进程仍在执行只是消耗的时间远超预期它可能还在消耗CPU、进行物理I/O。而Hang进程是指数据库会话Session因为某些原因被完全阻塞无法继续执行任何工作处于一种“等待-无法前进”的状态。从操作系统层面看该进程可能几乎没有CPU消耗主要处于睡眠Sleep状态。导致Hang的根本原因通常可以归结为会话在等待某个它永远或短期内无法获得的资源。在Oracle中这种等待被抽象为“等待事件”Wait Event。一个健康的数据库会话总是在“执行”On CPU和“等待”之间切换。但当某个等待事件持续时间异常长就成了我们需要关注的Hang。2.2 导致Hang的四大常见“元凶”根据我多年的经验Hang进程通常由以下几类问题引发理解它们是快速定位的第一步锁争用Lock Contention这是最经典的原因。例如会话A更新了表T的某一行但未提交会话B试图更新同一行就会挂起等待行锁enq: TX - row lock contention。更严重的是表级锁或系统内部锁争用。闩锁争用Latch Contention闩锁是Oracle内部一种轻量级的串行化控制机制用于保护内存结构。高并发访问热点块如索引根块可能引发闩锁争用如latch: cache buffers chains导致会话挂起。资源等待Resource Waits会话可能因为缺乏必要的系统资源而挂起。例如enq: HW - contention等待高水位线推进常见于大量插入且未使用自动段空间管理的表。enq: TX - allocate ITL entry等待事务槽ITL常见于高并发更新、初始INITRANS设置过小的表。log file sync提交时等待LGWR进程将日志缓冲区写入在线重做日志文件。如果I/O慢大量提交会导致会话堆积。内部错误或Bug相对少见但棘手。数据库内部状态异常或软件缺陷可能导致会话进入一种无响应的死循环状态。注意在动手“移除”任何会话之前首要任务是确认它是否真的在Hang以及Hang的原因。盲目Kill会话可能无法根治问题例如Kill掉一个等待行锁的会话后新会话可能立刻再次被同一个锁挂起甚至可能引发数据逻辑不一致如果Kill的是持有未提交事务的会话。3. 系统性排查Hang进程的实战流程当怀疑出现Hang进程时切忌慌乱。遵循一个系统性的排查流程可以高效地定位问题根源。以下是我在实际工作中总结的“四步定位法”。3.1 第一步快速定位问题会话与等待事件首先我们需要找到那个“有问题”的会话。Oracle提供了多个动态性能视图V$视图来帮助我们。核心查询1查找长时间非空闲等待的会话SELECT s.sid, s.serial#, s.username, s.program, s.machine, s.sql_id, s.event, s.seconds_in_wait, s.state, sq.sql_text FROM v$session s LEFT JOIN v$sql sq ON s.sql_id sq.sql_id WHERE s.type ! BACKGROUND AND s.state WAITING AND s.wait_class ! Idle -- 排除空闲等待类如‘SQL*Net message from client’ AND s.seconds_in_wait 60 -- 等待超过60秒阈值可根据情况调整 ORDER BY s.seconds_in_wait DESC;这个查询能快速列出所有正在经历非空闲等待、且等待时间较长的用户会话。重点关注event等待事件、seconds_in_wait等待秒数和sql_text正在执行的SQL。如果某个会话的seconds_in_wait值极高且其等待事件是上述提到的锁、闩锁类事件那它就是首要怀疑对象。核心查询2使用ASHActive Session History历史分析如果问题发生时你没有及时登录或者会话已经消失可以查询V$ACTIVE_SESSION_HISTORY或AWR报告。ASH默认每秒采样一次记录活动会话的等待事件。-- 查询过去15分钟内最热门的等待事件和对应的SQL SELECT sample_time, session_id, session_serial#, sql_id, event, COUNT(*) OVER (PARTITION BY sql_id, event) as wait_count FROM v$active_session_history WHERE sample_time SYSDATE - 15/1440 -- 过去15分钟 AND session_state WAITING AND wait_class ! Idle ORDER BY sample_time DESC;3.2 第二步深度剖析锁与阻塞链如果第一步的查询显示等待事件是enq: TX - row lock contention或其他锁相关事件那么下一步就是绘制阻塞链Blocking Chain。这是诊断锁问题的关键。核心查询查找锁的持有者和等待者SELECT -- 等待者信息 w.sid as waiting_sid, w.serial# as waiting_serial#, w.event as waiting_event, w.seconds_in_wait, -- 阻塞者信息 b.sid as blocking_sid, b.serial# as blocking_serial#, b.username as blocking_user, b.program as blocking_program, b.sql_id as blocking_sql_id, -- 锁信息 l.type as lock_type, l.id1, l.id2 -- 锁标识符可用于关联具体对象 FROM v$session w JOIN v$lock l ON w.sid l.sid AND l.request 0 -- 找到正在请求锁的会话等待者 JOIN v$lock l2 ON l.id1 l2.id1 AND l.id2 l2.id2 AND l2.request 0 -- 找到相同锁的持有者 JOIN v$session b ON l2.sid b.sid -- 关联持有者会话信息 WHERE w.state WAITING ORDER BY w.seconds_in_wait DESC;这个查询的结果会清晰地显示“谁被谁阻塞”。blocking_sid就是导致Hang的“罪魁祸首”。你需要进一步查看这个阻塞会话在做什么blocking_sql_id以及它是否处于活动状态。有时阻塞者本身可能也在等待其他资源形成一条长长的阻塞链你需要顺着链找到最头部的阻塞会话最终阻塞者。3.3 第三步关联SQL与对象理解业务上下文找到问题会话和SQL ID后需要理解它在操作什么。查看完整SQL使用V$SQLTEXT或DBA_HIST_SQLTEXT查看完整的SQL语句。分析它是在更新/删除哪张表条件是什么。定位锁定的行Row Lock如果怀疑是行锁可以通过以下查询尝试定位具体的行信息需要相应权限。SELECT do.object_name, row_wait_obj#, row_wait_file#, row_wait_block#, row_wait_row#, dbms_rowid.rowid_create(1, row_wait_obj#, row_wait_file#, row_wait_block#, row_wait_row#) as rowid FROM v$session WHERE sid blocking_sid -- 替换为阻塞者的SID AND row_wait_obj# 0;得到ROWID后可以进一步查询该行的内容了解业务数据情况。检查对象结构如果是enq: TX - allocate ITL entry等待检查相关表的INITRANS和PCTFREE参数是否设置合理。3.4 第四步评估影响与制定处置方案在动手前必须评估业务影响Hang的会话是否影响核心交易能否等待其自然完成阻塞链长度是一个会话被阻塞还是引发了一连串的“雪崩”阻塞者状态阻塞会话是在执行一个长时间事务如大批量更新还是它自己也Hang住了例如应用服务器崩溃导致连接异常会话僵死但事务未回滚基于评估方案通常是方案A治标移除Kill阻塞链头部的会话释放锁。这是解决当前紧急情况的常用手段。方案B治本联系阻塞会话对应的应用负责人协商让其提交或回滚事务。这更安全但可能耗时。方案C优化如果问题反复出现则需要从应用设计如事务粒度、提交频率、SQL优化如减少全表更新、对象参数调整如增加INITRANS等方面进行根治。4. 安全移除Hang进程的详细操作指南当我们决定采用“方案A”即移除会话时必须谨慎操作。Oracle提供了两种“Kill”会话的方式含义截然不同。4.1 两种“Kill”方式的区别与选择特性ALTER SYSTEM KILL SESSION sid,serial#ALTER SYSTEM DISCONNECT SESSION sid,serial# IMMEDIATE机制温和终止。向会话发送终止信号会话在下一个“断点”如执行完当前SQL、等待用户输入时自行清理并退出。强制断开。直接终止服务器端的专用服务器进程或标记共享服务器进程为终止并立即回滚其未提交事务。事务处理如果会话有未提交事务事务不会立即回滚会话标记为“KILLED”锁可能依然持有直到会话自己处理终止信号。立即回滚未提交事务释放所有锁。等待时间如果会话正在执行一个长循环或长时间操作可能很久才响应终止信号。几乎立即生效。适用场景希望会话优雅退出如前端应用可正常断开连接。或担心立即回滚大事务对系统产生冲击。需要立即释放资源解决紧急Hang问题。会话已无响应或僵死。风险可能无法立即解决锁争用因为锁可能未被释放。产生“KILLED”状态的僵尸会话。强制回滚可能产生大量重做日志Redo短期内对I/O有压力。实操心得在处理Hang进程以快速恢复业务时绝大多数情况下应首选DISCONNECT ... IMMEDIATE。因为它能确保锁被立即释放。只有在明确知道会话事务很小且希望应用层感知断开进行善后时才用KILL SESSION。4.2 分步操作与现场验证假设我们通过排查确定SID123, SERIAL#45678的会话是最终阻塞者需要移除。步骤1尝试温和终止可选根据情况跳过ALTER SYSTEM KILL SESSION 123,45678;执行后立即查询该会话状态SELECT sid, serial#, status, server, program FROM v$session WHERE sid123;如果STATUS变为KILLED但SERVER还是DEDICATED专用说明它还未完全清理。此时锁可能还在。步骤2强制断开推荐用于紧急HangALTER SYSTEM DISCONNECT SESSION 123,45678 IMMEDIATE;执行后再次查询该会话应该会从V$SESSION中消失。步骤3验证阻塞是否解除再次运行3.2节中的“阻塞链查询”。确认原先被阻塞的会话Waiting Sessions的等待事件是否已消失状态是否变为ACTIVE或INACTIVE。观察应用告警是否恢复。步骤4检查是否有事务回滚如果强制断开了一个活动事务Oracle会启动进程进行回滚。可以监控回滚进度SELECT usn, state, undoblockstotal “Total”, undoblocksdone “Done”, undoblocksdone/undoblockstotal*100 “Progress%” FROM v$fast_start_transactions;或者查看警报日志Alert Log会有类似ORA-1092 signalled during: ...的回滚信息。4.3 处理特殊顽固情况“KILLED”状态会话不释放有时执行KILL SESSION后会话状态变为KILLED但长时间不退出锁依然持有。这通常是因为会话正在等待一个网络I/O如客户端无响应。会话处于长事务回滚中。遇到了某些极端的Bug。强制清理手段谨慎使用 在操作系统层面找到该会话对应的服务进程Server Process的PID然后使用kill -9命令。-- 在数据库中查询会话的SPID操作系统进程ID SELECT s.sid, s.serial#, p.spid, s.program, s.username FROM v$session s, v$process p WHERE s.paddr p.addr AND s.sid 123;假设查询到SPID为98765。# 在数据库服务器操作系统Linux/Unix上执行 kill -9 98765警告kill -9是操作系统级的强制杀死信号数据库进程没有机会进行任何清理。这可能导致短暂的PMON清理延迟。极少数情况下可能导致共享内存段出现轻微不一致但Oracle的进程结构通常能抵御这种冲击。被杀死进程正在进行的任何I/O操作会被中断。因此这应作为最后的手段并最好在Oracle Support的指导下进行。5. 根因预防与长效优化策略移除Hang进程是“救火”更重要的是“防火”。以下是一些预防性措施和优化建议。5.1 应用设计与开发规范事务要短小精悍避免在应用层开启一个事务后进行长时间的人工操作或等待。遵循“尽快提交”原则。访问顺序标准化对于可能更新相同数据的多个程序约定以固定的顺序例如按主键升序访问数据可以避免死锁。使用SELECT FOR UPDATE NOWAIT/SKIP LOCKED在需要锁定的场景使用NOWAIT选项可以立即获得锁或报错返回而不是无限等待。SKIP LOCKED可以跳过已被锁定的行适合队列处理场景。设置合理的SQL超时在应用或中间件如连接池层面设置SQL执行超时避免一个坏查询无限期占用资源。5.2 数据库监控与告警配置部署关键等待事件监控持续监控enq: TX - row lock contention、enq: TM - contention、log file sync、latch: cache buffers chains等关键等待事件的平均等待时间或等待次数。设置阈值告警。使用ASH/AWR进行周期性分析定期查看AWR报告关注“Top Foreground Events”部分及时发现潜在的系统级锁或闩锁争用趋势。监控长时间运行的事务SELECT s.sid, s.serial#, s.username, s.program, t.start_time, t.used_ublk FROM v$session s, v$transaction t WHERE s.taddr t.addr AND t.start_time SYSDATE - INTERVAL 30 MINUTE; -- 运行超过30分钟的事务USED_UBLK列表示使用的回滚块数量值很大意味着有大量未提交的修改。5.3 数据库对象与参数优化合理设置表参数对于高并发更新的表适当增加INITRANS例如从默认的2增加到8或16以减少ITL争用。合理设置PCTFREE避免行迁移和行链接。索引优化热点索引可能引发缓冲链闩锁争用。考虑使用反向键索引Reverse Key Index或哈希分区索引来分散热点。日志文件与归档配置确保在线重做日志文件大小合适存放于高性能存储上避免log file sync等待。定期检查归档日志目的地是否有足够空间。5.4 建立标准应急响应流程为团队建立一套标准的Hang进程排查SOP标准作业程序确认现象收集告警信息、受影响业务范围。快速定位使用3.1节的脚本快速定位高等待会话和事件。分析阻塞链使用3.2节的脚本找到阻塞源头。评估与决策判断是否立即Kill还是联系应用方。执行操作使用DISCONNECT SESSION ... IMMEDIATE移除会话。观察恢复确认阻塞解除业务恢复。事后复盘分析根本原因推动应用或架构优化更新监控策略。处理Oracle数据库的Hang进程是一项融合了技术知识、经验判断和流程规范的工作。从快速精准的排查到安全果断的操作再到未雨绸缪的优化每一个环节都至关重要。记住ALTER SYSTEM KILL SESSION只是一个命令而真正重要的是下达这个命令之前你所做的全部分析和思考。每一次成功的“救火”都应该成为推动系统走向更稳健架构的契机。