ORA-03113/ORA-03137幽灵会话因SQL*Net未探测断连而残留ACTIVE状态,需配置SQLNET.EXPIRE_TIME、TCP.CONNECT_TIMEOUT等参数并统一RAC各节点,必要时kill -9 SPID强制清理。
ORA-03113/ORA-03137:连接断开后会话仍卡在ACTIVE状态
在Oracle RAC环境中,一个颇为恼人的现象是:客户端因为网络闪断、应用崩溃或者防火墙策略清理空闲连接而异常断开后,数据库的v$session视图里,却常常残留着一大批状态显示为‘ACTIVE’的会话。这些“幽灵会话”早已名存实亡,与客户端失去了联系,但它们既不响应常规的alter system kill session命令,也不受idle_time参数的限制。结果就是,它们持续占用着PGA内存、锁以及全局队列资源,在RAC这种共享架构下,极易引发全局缓存(GC)等待的连锁反应,拖累整个集群的性能。

问题的根源,往往不在于数据库层面没有检测能力,而在于SQL*Net网络层默认没有开启主动探测或超时清理机制。要知道,在默认配置下,TCP连接的保活(keepalive)是由操作系统控制的,比如Linux系统默认的2小时超时,这远远超出了绝大多数业务场景的容忍范围。
- 如何检查当前无效会话? 可以运行以下查询,找出那些状态为ACTIVE但最后一次调用已超过5分钟的会话:
SELECT sid, serial#, username, status, server, program, sql_id, last_call_et FROM v$session WHERE status = 'ACTIVE' AND last_call_et > 300 AND username IS NOT NULL;
- 如何确认会话是否真的无响应? 对于可疑的会话ID,查询
v$session_wait视图。如果发现其等待事件(event)是‘SQL*Net message from client’,并且状态(state)为‘WAITING’,但等待时间(seconds_in_wait)却在持续累加,这基本就能断定客户端已经失联了。 - RAC环境下的特有风险: 这类幽灵会话在集群实例间进行状态同步或争用全局资源时,可能会被其他节点反复尝试处理,无形中放大了集群的内部通信开销,让问题雪上加霜。
sqlnet.ora里必须配的3个超时参数
单纯依赖数据库的resource_limit和idle_time参数,对这类连接是无能为力的。必须在$ORACLE_HOME/network/admin/sqlnet.ora文件中显式启用SQL*Net层的探测机制。这里有三个关键参数,可以说是缺一不可,而且需要注意它们的生效范围:
SQLNET.EXPIRE_TIME = 10:这是核心。它指示服务器每隔10分钟向空闲的客户端连接发送一个探测包(一个空的数据包)。需要明确的是,这并非“10分钟后断开连接”,而只是发起一次探测。只有当客户端对这次探测毫无响应时,连接才会在后续被真正关闭。TCP.CONNECT_TIMEOUT = 60:这个参数主要影响新连接的建立阶段,比如TNS ping或首次登录尝试。对于已经建立的连接,它不起作用。但将其设置为一个合理的值(如60秒),可以有效避免因网络问题导致的大量连接请求堆积,阻塞监听器进程。TCP.VALIDNODE_CHECKING = YES配合TCP.INVITED_NODES = (your_app_subnet):这组参数虽然不直接控制超时,但通过限制允许连接的主机,可以从源头上防止恶意扫描建立虚假连接,间接减少了无效连接产生的可能性。
⚠️ 一个关键的大坑: 在RAC环境中,SQLNET.EXPIRE_TIME这个参数必须在所有节点上保持配置一致。否则,可能出现一个节点因探测失败断开了连接,而客户端重连时被分配到另一个节点,却可能错误地复用了旧的连接句柄,导致会话状态陷入混乱。修改完sqlnet.ora后,数据库实例无需重启,但必须重启监听器(lsnrctl)以使新配置生效。
ALTER SYSTEM KILL SESSION不生效?直接查SPID杀OS进程
有时候,你会发现v$session中的会话状态已经变成了‘KILLED’,但它占用的资源却迟迟没有释放。这在RAC环境中尤其常见,可能因为会话涉及跨实例的锁等待。此时,干等着PMON进程来清理可不是好主意,更主动的做法是定位到对应的操作系统进程并强制终止。
- 第一步,查找操作系统进程ID(SPID):
SELECT s.sid, s.serial#, s.status, p.spid, s.program FROM v$session s, v$process p WHERE s.paddr = p.addr AND s.sid =
; - 第二步,在会话所在的RAC节点上执行强制终止: 在Linux系统上使用
kill -9;在Windows系统上则使用orakill。 - 第三步,验证清理结果: 执行
SELECT count(*) FROM v$process WHERE spid = ',如果返回结果为0,就说明进程已被成功清除。';
需要理解的是,ALTER SYSTEM KILL SESSION命令本身只是给会话打上一个“待清除”的标记,真正的清理工作是由后台进程PMON周期性地扫描来完成的(默认每3秒一次)。如果这个会话恰好持有未提交的DML锁,或者正卡在两阶段提交的中间状态,PMON的清理动作可能会被延迟数分钟甚至更久。在这种情况下,强制杀掉操作系统进程往往是唯一立竿见影的方法。当然,操作前最好通过v$transaction视图确认一下该会话是否还有未提交的事务,以评估强制终止的风险。
监听器日志里看真实断连时间点
数据库内部的视图只能告诉我们会话“何时被标记为失效”,如果想追踪网络层实际断开连接的确切时刻,就必须去查阅$ORACLE_HOME/network/log/listener.log这个监听器日志文件。
- 搜索关键错误码: 重点关注
TNS-12535(操作超时)、TNS-12608(发送超时)以及Failed to accept a connection这类信息。 - 典型的日志记录如下:
TNS-12535: TNS:operation timed out TNS-12608: TNS:send timeout ns secondary err code: 12535 nt main err code: 0
- 结合时间戳和客户端IP地址进行分析, 可以反向推断出问题的源头:到底是应用程序侧的心跳机制缺失,还是中间的负载均衡设备(如F5、云负载均衡器)主动切断了连接。如果同一个IP地址频繁出现
TNS-12535错误,那么很大概率是应用程序没有正确关闭连接,或者连接池的超时配置设置得过长。
真正棘手的是那些“隐形”的连接——它们既没有在监听器日志中留下错误记录,也没有被SQLNET.EXPIRE_TIME机制捕获。这类连接往往卡在操作系统的TCP FIN_WAIT2状态。排查它们需要从操作系统层面入手,例如使用netstat -an | grep :1521 | grep FIN_WAIT2这样的命令。遇到这种情况,调整的焦点通常就不再是数据库参数了,而是操作系统的内核网络参数,比如net.ipv4.tcp_fin_timeout。
