必须加 sql_id IS NULL 是因为该等待事件包含正在执行慢SQL的会话,混入后会掩盖真正空闲等待连接池问题的会话;program 以 java@ 开头且 sql_id 为空、avg_ms > 100、多会话同机集中长等待,表明Java连接池配置不当或泄漏;gv$session 补充查 status='INACTIVE' 和 logon_time 突增可识别连接未释放或认证风暴;ASH时间窗口需足够(如查2分钟)、跨RAC须按 inst_id 分查,且数据仅内存保留约30分钟。

查 SQL*Net message from client 长等待时,为什么必须加 sql_id IS NULL
因为这个等待事件本身不区分“是否在执行 SQL”——只要客户端发完请求、正在等响应,就会卡在这里。如果漏掉 sql_id IS NULL,结果里会混入大量正在执行慢 SQL 的会话,它们的等待其实是 SQL 执行耗时,不是连接池问题。真正要抓的是那些“没在跑 SQL,却卡在等客户端”的空转会话,这才是连接池未复用、建连断连或认证风暴的典型信号。
按 machine 和 program 分组统计时,怎么识别 Java 连接池异常
Oracle 的 program 字段常带客户端进程名,比如 java@web01、python@app2。对 Java 应用,重点看两类现象:
-
program以java@开头,但sql_id为空、event是SQL*Net message from client、且avg_ms > 100:大概率是连接池配置过小或未启用,导致频繁新建物理连接 - 同一
machine下多个会话同时出现长等待,且blocking_session IS NULL:说明不是数据库内阻塞,而是客户端侧 TCP 连接卡在链路上,比如连接池泄漏后不断重连,触发服务端端口耗尽或防火墙拦截
为什么不能只查 v$active_session_history,还要关注 gv$session 中的 status 和 logon_time
ASH 是采样快照,只能看到“某一秒它在等”,但看不到“它什么时候连上来、连了多久、有没有被应用主动关闭”。这时候要补查 gv$session:
-
status = 'INACTIVE'且last_call_et > 300(5分钟):说明连接空闲很久却没释放,是典型的连接池未回收或应用未 close() 连接 -
logon_time集中在某几分钟内突增:比如 14:22–14:23 有 80+ 个新会话,而平时每分钟不到 5 个,基本可判定是连接池重建、配置变更或认证失败风暴(如密码错误触发重试) -
username为空但program是java@:说明是后台连接池线程,不是业务用户登录,进一步排除权限或账号问题
执行 ASH 查询时,最容易忽略的三个时间陷阱
ASH 数据存在内存环形缓冲区,查错时间范围会导致关键样本丢失或误判:
- 用
SYSDATE - 1/1440(1 分钟)查SQL*Net message from client,但实际问题持续 90 秒:最后 30 秒的样本可能已被覆盖,应至少查SYSDATE - 2/1440(2 分钟)并确认GV$ACTIVE_SESSION_HISTORY中最大sample_time确实落在目标窗口内 - 跨 RAC 节点查询时,不同节点系统时间若偏差 > 1 秒,
sample_time对齐会出错,必须用inst_id分开查再合并,不能直接WHERE sample_time BETWEEN ...全局过滤 - 生产库默认保留 ASH 数据约 30 分钟(取决于内存压力),不是所有环境都支持查 1 小时前的数据;想长期归档需手动调用
DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT(),否则历史无法回溯
真实问题往往藏在 machine 和 program 的组合细节里,而不是等待事件名称本身;ASH 给的是线索,不是结论,得结合连接池配置、应用日志和网络拓扑一起交叉验证。


















