1. 项目概述为什么 Oracle 会话会杀不掉做 Oracle 运维的几乎都遇到过这种场景一张大表被某个会话锁住应用侧疯狂报错业务方在群里连环呼叫你登录数据库执行alter system kill session返回的是Session killed你以为完事了结果一查v$session那个会话还在那儿挂着状态栏写着KILLED锁住的资源纹丝不动应用依然在报错。这个终极篇的由来就是因为这个。说得直白一点常规的kill session只是给会话发了一个请退出的信号相当于你礼貌地请一个占着座位不走的人离开。他要是听劝那皆大欢喜他要是耳机戴着、音乐放得正嗨你的礼貌请求就会被无视。更麻烦的是有些会话正卡在内核态、网络 IO 或者某种等待事件里压根来不及响应你的信号。这时候就要上真正的终极手段了。这篇文章写给谁给那些已经会用 Oracle、但被杀不掉的会话折磨过的朋友给那些想系统性地搞清楚KILLED状态背后机制的人给那些希望在实际生产环境里遇到这种问题时不再手足无措、能拿出成套解决方案的 DBA。不管你是刚入行的运维新手还是已经独立管库的老司机这套从常规手段到终极手段的完整链路都值得收藏一份。我把这些年从杀个会话都要重启实例的坑里爬出来的经验全部整理在这里了。2. 核心思路先搞清楚会话到底是个什么结构2.1 会话不是你以为的那一个连接很多人对会话的理解就是应用连到数据库的那根线其实在 Oracle 内部这个概念要拆成三层看。最外层是客户端的连接进程比如 JDBC 连接池里的物理连接或者 sqlplus 启动的那个进程。中间层才是我们常说的会话即v$session里记录的那一行它包含 SID、SERIAL#、状态、等待事件等一堆属性。最底层是 Oracle 真正干活的影子进程shadow process在 Linux/Unix 上对应一个操作系统进程在 Windows 上对应一个线程。这三层是联动的。你执行alter system kill session实际上是往中间层这个会话结构里注入一个标记让它退出。如果这个会话当前正在执行一条 SQL执行到一半被标记为取消那么 Oracle 会尝试回滚事务、清理游标、释放锁资源然后断开客户端连接。这个过程如果顺利几秒钟内就能完成。如果不顺利比如事务特别大、回滚需要几十分钟或者会话卡在某种无法打断的等待上那这个标记就一直挂着表现出来就是SESSION KILLED但还占着一个进程。理解了这三层结构你就明白为什么杀了还会看到——因为杀的是会话但真正占着 CPU、内存、锁和 undo 的是底层的影子进程。杀掉这个进程才是真正的终结。2.2 理解状态ACTIVE、INACTIVE、KILLED 分别意味着什么在v$session里状态字段是最直观的诊断入口。ACTIVE会话正在执行 SQL占用 CPU 或者处于某种活动状态的等待。这种会话一般杀起来需要点时间因为它正干着活。INACTIVE会话空闲客户端连接还开着但啥也没干。这种通常一杀就掉不过要注意有些连接池会保持大量INACTIVE会话这类会话数量多没关系关键看它锁了什么。KILLED已经被标记杀死但还没有被清理掉。这是最让 DBA 头疼的状态需要判断它是在回滚中马上就好还是彻底卡死了永远出不去。判断一个KILLED状态会话的真实进度可以看它在做什么。通常来说一个正在回滚大事务的KILLED会话在v$session里会显示ACTIVE或者KILLED同时它的事务信息里回滚段的使用量会逐渐下降v$transaction里能看到它的 undo 变化。如果长时间卡着一直不动比如过了半个小时回滚字节数一点不降那大概率是出了问题。2.3 为什么会产生杀不掉的现象杀不掉的会话常见的诱因就那么几类。第一类会话卡在不可中断的内核操作上。比如某些系统调用、网络读取、异步 IO 等待。这种情况下操作系统层面都拿它没太多办法Oracle 的信号发不进去它自己也感知不到死亡标记就一直挂着。最典型的是会话在等一个客户端的数据网络断了但客户端没按规范退出TCP 连接又没设置超时这个会话可能会一直保持INACTIVE状态好几天都不消失。第二类回滚工作量极大。一个大事务比如批量更新了上千万行执行更新用了五分钟杀会话触发回滚回滚可能就需要二十分钟甚至更久。这种情况下不是会话杀不掉而是它在杀完自己的尾巴需要等。有经验的 DBA 会通过设置_smu_debug_mode等隐藏参数来加速回滚但这是高风险的操刀手段后面细说。第三类存在跨实例锁残留。RAC 环境下一个会话的锁可能挂在其实例节点上另一个节点上操作杀会话信号传递链路比较长节点间通信有故障时这个标记就卡住了。第四类某些特定等待事件下比如gevent、kstimer相关的等待Oracle 的信号机制无法及时打断表现为怎么杀都杀不掉。理解了这个底层逻辑剩下的所有操作步骤其实都是在围绕怎么让底层进程真正退出展开。3. 常规手段详解ALTER SYSTEM KILL SESSION 的正确用法3.1 基本语法与参数含义标准用法是ALTER SYSTEM KILL SESSION sid,serial# [IMMEDIATE];sid和serial#从v$session查两个值组合起来才能唯一定位一个会话。很多人只记 sid 不记 serial其实同一个会话断开重连后sid 可能被复用但 serial 变了所以两个必须一起给。IMMEDIATE参数的作用是让 Oracle 快速回滚事务并释放相关资源不等会话自己退出。加了IMMEDIATEOracle 会立刻事务回滚断开客户端正常情况下效果立竿见影。不加的话是让会话结束时自己感知如果它一直不感知可能等半天。实操经验对于确定要杀的会话我几乎都会加IMMEDIATE除非是回滚量特别大、希望它慢慢回滚以免影响系统整体负载的场景但这种情况其实更推荐用别的手段后面会提到。3.2 如何快速定位目标会话的三板斧杀会话之前先得确认杀谁。定位会话的工具有三个层面。第一层业务侧报出来的信息。通常应用会告诉你这个报错来自哪个模块通过v$session的PROGRAM、MODULE、ACTION字段可以模糊匹配。比如某公司财务系统在跑月末结账对应MODULE可能是 FinanceMonthEndACTION可能是 Settlement直接用这三个字段过滤就能缩小范围。第二层通过锁冲突定位。找被阻塞的会话再反向找阻塞者。经典 SQL 是查v$lock和v$session关联看某个等待enq: TX - row lock contention的会话的BLOCKING_SESSION字段直接指向阻塞者。这招在生产排障里最实用很多时候业务方只告诉你有东西卡住了你通过阻塞树一层层拉上去就找到了源头会话。第三层直接通过操作系统进程反查。拿到影子进程的 SPID服务端进程 ID在操作系统上用ps -ef查看它对应的进程、启动时间、CPU 使用率尤其当 Oracle 内部视图里查不到有效等待事件时OS 层面往往能看出端倪比如进程 CPU 高得离谱或者进程已经变成僵尸状态。3.3 实测现场常规杀会话的完整操作路径我模拟一个真实场景来演示整个流程。某个下午某财务系统突然大批量报错错误是ORA-00060: deadlock detected while waiting for resource之后伴随着一堆resource busy and acquire with NOWAIT specified。我登录数据库先查当前锁冲突情况SELECT s.sid, s.serial#, s.username, s.program, s.status, s.blocking_session, s.event, s.seconds_in_wait FROM v$session s WHERE s.blocking_session IS NOT NULL ORDER BY s.seconds_in_wait DESC;查出几个会话在等待TX- Row Lock contention然后继续往上传最终揪出一个ACTIVE状态、正在跑一条超大UPDATE的会话。仔细一看这个会话更新了某张表的上亿行数据还在进行中。我对它执行ALTER SYSTEM KILL SESSION 1234,56789 IMMEDIATE;执行完几秒钟内v$session里这个会话的状态变成KILLED但进程还在。看等待事件是pmon timer说明清理正在由后台进程逐步进行。我等了大概十几分钟看到它的回滚信息在v$transaction里的 undo 大小持续下降最终状态消失锁释放业务恢复。这就是一个常规手段能解决但需要耐心的典型场景。真正让人头疼的是那种执行完命令之后状态一直是KILLED等待事件也看不到任何变化回滚信息也没有就像被冻住了。这时候就得动用系统层面的大招了。4. 终极手段操作系统层面强制终止会话4.1 为什么 ALTER SYSTEM 不够用了当你遇到会话状态为KILLED但完全不消失或者即使不是KILLED状态ALTER SYSTEM KILL SESSION都返回不了正常结果的极端情况时说明 Oracle 的信号机制已经失效了。失效的原因可能是会话本身卡死在内核态根本没法响应信号可能是会话在某个等待队列里被挂死了Oracle 后台进程清理它也一直没进展还有可能是 RAC 节点间的通信问题导致 kill 标记传递不完整。这时候思路要转变与其在 Oracle 内部和它死磕不如到操作系统层面直接拔电源。Oracle 会话的三层结构里底层那个影子进程才是实体把这个进程干掉会话就是不死也得死。4.2 启用 OS 级杀进程前的必要确认不经过确认就随手kill -9是要出事的。至少要做以下三件事。确认这个进程确实属于目标会话。通过v$process关联v$session拿到 SPIDSELECT p.spid, s.sid, s.serial#, s.username, s.program, s.status, s.event FROM v$process p, v$session s WHERE p.addr s.paddr AND s.sid 目标SID;这一步是防止杀错进程、误伤其他会话。确认这个会话执行的事务是不是真的可以放弃。如果会话是重要批处理的一部分并且业务方明确不能丢数据那么 OS 级杀掉进程会强制回滚事务回滚量极大时可能给数据库带来巨大负载。这种时候执行的底线是宁可等它慢慢回滚也不要硬杀。如果业务方已经确认这个事务可以放弃那就放开手干。确认数据库处于归档模式且数据文件状态正常。杀进程本身不会损坏数据文件但如果这个进程正持有某些关键资源比如 redo 写入或控制文件更新中途拔掉会造成实例恢复恢复时间可能远超预期。所以操作前最好确认当前有没有其他异常状态比如磁盘满、redo 无法切换等。4.3 Linux/Unix 环境下的终极操作方式确认无误后在操作系统上找到目标进程执行kill -9 12345这里的12345就是v$process里查到的 SPID。执行后你可以立刻回到数据库查v$session通常情况下这个会话会消失或者显示为KILLED但伴随PMON清理动作。如果几分钟后还是没有变化可以再对 Oracle 后台执行一次常规的 kill 确认ALTER SYSTEM KILL SESSION 1234,56789 IMMEDIATE;有些时候kill -9之后 PMON 没有意识到进程没了还需要一个推动力来唤醒清理机制。再执行一次常规 kill 命令就是这个推动力——虽然它大概率返回Session killed但实际作用是触发 PMON 重新检查。如果连 PMON 都懒得动那最后的大招是停掉实例或者强制取消实例恢复。这一步风险极高除非你确认这个节点可以被终止且应用可以接受服务中断否则不做。4.4 Windows 环境下的终极操作方式Windows 环境没有kill -9对应的是taskkill /F。taskkill /F /PID 12345或者用wmic process where processid12345 call terminate效果一样。需要注意的坑是Windows 上如果影子进程对应的是一个线程而不是独立 PID你需要先确认 Oracle 是否以多线程模式运行。如果是 Windows 多线程模式操作思路要改成在 Oracle 内部杀掉整个实例的线程组这在实际生产里极其少见但对某些 Windows 上的 Oracle 安装偶尔会遇到。无论哪种系统环境OS 级强杀只是手段不是目的。强杀之后一定有 PMON 或资源清理的过程这个过程需要时间。别杀完就急着宣布搞定了多观察一段时间。5. 特殊场景实战被挂起等待事件和 RAC 环境怎么处理5.1 怎么杀都杀不掉的真实案例分析某次生产故障有个会话锁住了一张核心业务表导致整个应用业务停摆。我执行ALTER SYSTEM KILL SESSION后会话状态立刻变成KILLED但等待事件却是library cache lock而且这个状态持续了三十分钟锁一点没释放。查了v$lock发现这个会话在之前对一张表执行ALTER TABLE ... MOVE操作期间其他会话访问这张表全部挤在一个DDL相关的锁等待上。杀掉它之后由于它执行到一半的 DDL 需要一个库缓存对象访问的排他权而另一个会话正在持有这个对象上的共享访问权死锁在一个库里谁也等不到谁。这个问题的标准解法是找到那个持有共享锁的会话也一并处理掉。但现场那个持有共享锁的会话其实只是一个普通的SELECT查询它也已经被之前的锁阻塞住了。于是我对两个会话都执行了 kill。一个是KILLED状态但卡在库缓存锁上一个是INACTIVE但持锁不松。两个都通过 OS 层面找到 SPID执行了kill -9。两个进程消失之后锁清理在几秒钟内完成业务恢复。这段经历给我的教训是杀会话不能只看目标会话自己要把整个锁等待链理顺。杀掉阻塞链末端的会话有时候不如把链上关键节点都处理干净来得有效。5.2 RAC 环境下跨节点杀会话的注意事项RAC 环境下处理杀不掉的会话复杂程度翻倍。几个常见坑如下。第一会话的INST_ID和SID, SERIAL#必须同时指定因为不同节点上会出现重复的 SID。我的习惯是专门写一条带INST_ID条件的 SQL先把会话精确定位到具体实例再做操作。第二跨节点信号传递依赖LMON等一系列后台进程。如果你在节点 1 上执行 kill而会话实际运行在节点 2kill 命令最终会传给节点 2 的 PMON 去处理。这个传递链路如果出现异常不会影响命令返回结果但实际清理动作可能滞后。第三如果有节点正处于 abnormal shutdown 状态跨实例锁和会话残留信息会更混乱。这时候优先处理的是锁而不是会话。我遇到过一次节点 2 崩溃后节点 1 上有个会话状态异常锁一直不释放应用疯狂报错。通过 OS 层面在节点 1 上强杀该进程后RAC 层面的锁协调自动进行最终恢复正常。RAC 环境的核心经验操作一定要带上INST_ID诊断一定要看全局视图gv$session、gv$lock任何操作前都要确认目标会话在哪个节点上活着。5.3 回滚导致 KILLED 会话迟迟不消失的破局思路还有一种常见场景会议室KILLED状态但它其实正在回滚一个大事务等待时间完全取决于事务大小。这时候的操作策略不是加急而是监控引导。我的一种做法是先看回滚进度。通过v$transaction里的USED_UBLK字段对比前后变化判断回滚在不在推进。如果推进明显那就不干预给点耐心。如果推进极慢或者停滞考虑是否有其他锁在阻碍回滚。极端情况下调低_smu_debug_mode或者对滚回滚段相关参数进行干预。但这类隐藏参数的操作风险极高没有充分把握不要乱用。我通常只有在业务已经明确再等下去损失更大时才考虑这种激进的手段。还有一个小技巧如果回滚过程被某个等待事件卡住可以通过oradebug setospid直接跟踪到这个进程的调用栈定位卡住的点。我曾经用这个方式定位到一次回滚卡在buffer busy waits上原因是某数据块上的热块竞争换了个方向处理才好。6. 常见问题与排查技巧实录我踩过的坑和总结出的经验6.1 为什么 KILL 后仍然能看到会话这个问题被问过无数次。原因分成两种情况。一种情况是会话状态显示KILLED但底层进程还在尚未被 PMON 清理。这个阶段你在v$session里能看到它等待事件通常是pmon timer之类的后台睡眠事件说明 PMON 还在做回收工作。这种情况下你需要做的是等待和监控而不是继续追加 kill。追加 kill 不会加快 PMON 的处理进度反而可能产生无意义的重复行为。另一种情况是进程已经消失但会话会话信息在视图里有延迟。某些场景下v$session的清理依赖 SGA 里状态的刷新有延迟是正常的。你可以在v$process里确认对应进程是否已经不存在了如果进程没了会话信息只是延迟消失的表现不用过度担忧。我一般用一条 SQL 做秒级确认SELECT p.spid, s.sid, s.serial#, s.status FROM v$session s, v$process p WHERE s.paddr p.addr AND s.sid 目标SID;如果结果集为空说明会话已经彻底清理一切正常。6.2 杀会话的正确姿势排序我在实际运维中形成了一套稳妥的操作优先级在这里分享给大家参考。第一优先常规ALTER SYSTEM KILL SESSION。适用于绝大多数场景操作简单风险低Oracle 内部机制能处理大多数情况。第二优先带IMMEDIATE参数的重试。如果常规命令执行后长时间不生效可以对同一个会话再次执行带IMMEDIATE的 kill起到唤醒和强调的作用。第三优先操作系统层面kill -9/taskkill /F。适用于确认无法通过 Oracle 内部机制清理的会话。操作前务必完成前面说的三步确认确认对象无误、事务可放弃、数据库状态正常。第四优先实例级操作。当目标进程已经无法通过单个进程处理或者整个节点出现异常时需要评估是否重启实例。这一步应该在负责人知晓并评估过影响面之后才进行。这个排序不是死板的。实际场景里如果业务已经全面停摆等不起前两步的尝试可以直接跳到第三步。我刚才讲的案例里很多都是直接跨过第二步上 OS 级强杀。6.3 杀会话可能导致的不良影响很多人把杀会话想得太轻以为杀完就完其实风险还是不少。最容易出现的问题是连接回收风暴。如果你杀掉的是一个应用连接池里的核心会话应用侧会建立大量新连接来补充。短时间内连接数激增可能会让监听器或者数据库的进程上限被打爆反而引发新一轮故障。所以杀完会话后关注一下v$session的总量变化以及监听器的日志。第二个问题是事务回滚带来的 IO 冲击。被杀的会话有一个大事务在回滚回滚过程需要大量读取 undo 表空间、写数据文件。这个 IO 压力可能让原本已经很脆弱的库雪上加霜。我的习惯是如果评估到回滚量大杀之前先看当前的 IO 负载如果 IO 已经接近瓶颈跟业务方确认后也许会选个低峰期再动手。但这在生产高优先级故障里很难做到所以只能尽量预留 buffer。第三个问题是分布式事务的残留。如果被杀的会话参与了 XA 分布式事务简单杀掉进程可能留下 in-doubt 事务后续需要手动清理dba_2pc_pending里的遗留记录。这种情况虽然少见但出现时很棘手处理的过程比杀会话本身复杂得多。6.4 两套实用脚本方便直接抄作业我自己一直在用的定位脚本分享出来给大家参考。查某个数据库用户下所有活跃会话的信息SELECT s.sid, s.serial#, s.username, s.program, s.module, s.action, s.status, s.event, s.seconds_in_wait, s.sql_id FROM v$session s WHERE s.username 某用户 AND s.status ACTIVE ORDER BY s.seconds_in_wait DESC;再一个是杀会话前会确认目标进程 OS 信息的关键脚本SELECT s.sid, s.serial#, s.username, s.program, s.event, p.spid, p.osuser, p.terminal, p.background FROM v$session s, v$process p WHERE s.paddr p.addr AND s.sid 目标SID;如果你需要同时杀一批会话比如某模块的会话全部有异常可以拼一段动态 SQL 来批量生成 kill 语句SELECT ALTER SYSTEM KILL SESSION || s.sid || , || s.serial# || IMMEDIATE; FROM v$session s WHERE s.module 异常模块名;这种动态生成 SQL 的方式很实用尤其是在处理连接池里大量异常 INACTIVE 会话时比一条条手写强太多。6.5 我总结的防患于未然建议杀会话是亡羊补牢真正合格的 DBA 应该把功夫放在预防上。第一给应用连接池配置合理的超时和回收参数。很多 INACTIVE 会话杀不掉根源是客户端连接池没有设置空闲超时导致一堆空闲连接长期挂在数据库上。让应用侧合理配置连接生命周期能减少大量无谓的会话残留。第二监控锁等待和阻塞会话。写一个定期巡检 SQL每五分钟检查一下blocking_session非空的会话一旦有阻塞立即告警。这个能做到的团队大部分锁故障都会在业务方感知之前先暴露出来。第三对重要的批量事务设置合理的回滚预期。给业务方培训时我反复强调如果预判一个批量操作会跑很久务必拆分成小事务分批提交。大事务不仅跑得慢杀起来也费劲这是生产事故里最常见的隐患之一。第四定期检查v$session里异常状态的会话尤其是KILLED超过一定时间不清理的。生产环境里这种僵尸会话往往是下一次故障的导火索。我见过太多团队平时不查一出事就急于杀会话杀不掉就重启实例——这样的暴力运维方式本质上是没有建立一个良性的监控机制。把这篇文章里的手段吃透再把预防的功夫做足你会发现所谓终极篇其实也不是很难。7. 写在最后一点个人心得做数据库运维这几年杀会话这个操作我从最初的照葫芦画瓢到后来把v$session、v$process、v$lock这些视图的字段烂熟于心再到能通过等待事件和 OS 层进程状态综合判断一个会话的真实处境走了不少弯路。回头看最深的体会是杀会话的技术并不复杂难的是判断该不该杀、能不能杀、杀了之后怎么善后。用我常说的一句话收个尾一个成熟的 DBA 不是靠kill -9解决所有问题而是靠对会话、事务、锁和系统资源的理解把每一个操作的风险都控制在自己能处理的范围之内。希望这篇终极篇能让你的杀会话之路走得更稳少踩几个我踩过的坑。