Oracle 慢 SQL 已经结束了,还能查到客户端 IP 吗?

举报
Lucifer三思而后行 发表于 2026/10/09 11:00:01 2026/10/09
【摘要】 前言上一篇文章分享了一个排查 Oracle 慢 SQL 来源 IP 的方法。当时接管一套 Oracle 11g 数据库,发现一条执行比较慢的 SQL,SQL_ID 是 fq5vvra6z6rt7。通过 V$SESSION 找到 SID,再关联 V$PROCESS 获取 SPID,最后在 Linux 上通过 netstat 查到了客户端 IP:10.245.40.123。整个过程比较简单,几...

前言

上一篇文章分享了一个排查 Oracle 慢 SQL 来源 IP 的方法。

当时接管一套 Oracle 11g 数据库,发现一条执行比较慢的 SQL,SQL_ID 是 fq5vvra6z6rt7。通过 V$SESSION 找到 SID,再关联 V$PROCESS 获取 SPID,最后在 Linux 上通过 netstat 查到了客户端 IP:10.245.40.123。

整个过程比较简单,几条命令就解决了。

不过这个方法有个前提:数据库会话还在,对应的网络连接也没有断开。

如果 SQL 已经执行结束,甚至会话都断开了,还能不能找到当时的客户端 IP?

另外,现在很多业务都是 Java 应用,通过连接池访问 Oracle。数据库看到的往往只是应用服务器 IP,如果想继续找到真正发起请求的客户端,又该怎么查?

最近专门翻了一下 Oracle 官方文档、Ask TOM 和网上的一些讨论,发现这两个问题还真没有想象中那么简单。

一、SQL 执行结束了,先看看 ASH

假设现在只有一个 SQL_ID,SQL 已经执行结束,V$SESSION 里找不到对应会话。

这种情况下,我一般会先想到 ASH。

Oracle 的 V$ACTIVE_SESSION_HISTORY 会对活跃会话进行采样,记录当时执行的 SQL_ID、SID、等待事件、客户端机器名等信息。

即使 SQL 已经执行结束,只要采样记录还在,就有机会找到它当时对应的会话。

SET LINES 220 PAGES 100

SELECT TO_CHAR(sample_time,'YYYY-MM-DD HH24:MI:SS') sample_time,
       session_id,
       session_serial#,
       sql_id,
       machine,
       program,
       module,
       client_id
FROM v$active_session_history
WHERE sql_id = 'fq5vvra6z6rt7'
ORDER BY sample_time DESC;

这里重点看 MACHINE、PROGRAM 和 CLIENT_ID。

假设查出来是这样的:

SQL_ID         MACHINE         PROGRAM
-------------  --------------  ----------------
fq5vvra6z6rt7  APP-SERVER-01   JDBC Thin Client

至少说明这条 SQL 曾经在 APP-SERVER-01 对应的会话中执行过。

但问题也来了,MACHINE 通常是机器名,不一定是 IP。

网上有一种方法,是使用 UTL_INADDR.GET_HOST_ADDRESS 将机器名解析成 IP:

SELECT DISTINCT
       machine,
       UTL_INADDR.GET_HOST_ADDRESS(machine) ip
FROM v$active_session_history
WHERE sql_id = 'fq5vvra6z6rt7';

这个方法可以试,但不能完全依赖。

比如机器名无法解析,或者应用服务器有多个网卡,甚至 DNS 记录已经变更,最终查出来的 IP 就不一定是当时连接数据库使用的地址。

所以我更倾向于把 ASH 查到的 MACHINE 当成一个线索,再结合其他记录确认。

还有一点,ASH 是采样,不是把每条 SQL 都记录下来。如果 SQL 执行时间很短,可能根本没有被采样到。

查不到记录也很正常。

二、ASH 查不到,再翻 AWR

如果 SQL 是昨天甚至几天前执行的,内存中的 ASH 可能已经没有记录了。

这时候可以继续查 DBA_HIST_ACTIVE_SESS_HISTORY。

SET LINES 220 PAGES 100

SELECT TO_CHAR(sample_time,'YYYY-MM-DD HH24:MI:SS') sample_time,
       instance_number,
       session_id,
       session_serial#,
       sql_id,
       machine,
       program,
       module,
       client_id
FROM dba_hist_active_sess_history
WHERE sql_id = 'fq5vvra6z6rt7'
  AND sample_time >= SYSDATE - 7
ORDER BY sample_time DESC;

相比内存 ASH,AWR 可以查询更早的历史记录。

不过要注意,AWR 保存的 ASH 数据也是采样记录,而且并不是把内存中的每一条 ASH 都完整保存下来。

因此,AWR 查不到,也不能说明这条 SQL 没执行过。

另外,同一个 SQL_ID 可能被多个应用服务器执行过。假设一条 SQL 每天执行几千次,直接按 SQL_ID 查询,可能返回很多不同的 SID 和 MACHINE。

这种情况下,最好先确认业务反馈的时间。

例如业务说昨天 14:30 左右出现卡顿,就把查询范围限制在 14:25 ~ 14:35:

AND sample_time BETWEEN
    TO_DATE('2026-10-07 14:25:00','YYYY-MM-DD HH24:MI:SS')
AND TO_DATE('2026-10-07 14:35:00','YYYY-MM-DD HH24:MI:SS')

再结合 SID、SERIAL#、实例编号,以及有记录时的 SQL_EXEC_ID、SQL_EXEC_START,缩小排查范围。

不过查到这里,通常还是机器名、程序名和会话信息,并没有直接拿到历史连接 IP。

如果确实需要确认当时的网络来源,就得继续往下找。

补充一句:ASH、AWR 涉及 Oracle Diagnostics Pack 授权,生产环境使用前需要确认授权情况。*

三、会话都断开了,监听日志可能还有记录

Oracle Listener 日志是另一个可以利用的地方。

正常情况下,客户端连接 Oracle 时,监听日志可能记录来源 IP、端口、服务名和连接时间。

先找到监听日志:

lsnrctl status

Oracle 11g 使用 ADR 时,日志通常在:

$ORACLE_BASE/diag/tnslsnr/<hostname>/listener/trace/listener.log

实际路径以监听配置为准。

可以先看看最近的连接记录:

grep 'establish' listener.log | tail -20

假设日志里有这样一条记录:

07-OCT-2026 14:30:12 *
(CONNECT_DATA=(SERVICE_NAME=repdb)) *
(ADDRESS=(PROTOCOL=tcp)(HOST=10.245.40.123)(PORT=45036)) *
establish * repdb * 0

其中 HOST=10.245.40.123 就是监听收到连接请求时记录的来源 IP。

看起来问题解决了,但这里其实还有个坑:监听日志记录的是连接,不是 SQL。

假设应用服务器上午 9 点就通过连接池建立了 20 条数据库连接,下午 3 点业务执行了一条慢 SQL,那么监听日志记录的可能是上午 9 点的连接,而不是下午 3 点执行 SQL 的时间。

如果只拿 SQL 执行时间去匹配监听日志,很可能什么也查不到,而且同一个应用账号可能有几十甚至几百条连接,仅凭监听日志中的 IP,也不一定能确定是哪条连接执行了这条 SQL。

因此,监听日志更适合确认历史连接来源。如果要进一步对应到具体 SQL,还需要结合会话、连接时间、应用日志等信息。

如果监听日志已经清理,或者当时没有开启相关日志,那就没有办法从这里补查了。

四、数据库审计有没有帮助?

除了监听日志,Oracle 11g 还可以看看 DBA_AUDIT_SESSION。

前提是数据库之前已经开启了相应的会话审计。

SET LINES 220 PAGES 100

SELECT username,
       userhost,
       terminal,
       client_id,
       sessionid,
       TO_CHAR(timestamp,'YYYY-MM-DD HH24:MI:SS') logon_time,
       TO_CHAR(logoff_time,'YYYY-MM-DD HH24:MI:SS') logoff_time,
       returncode
FROM dba_audit_session
WHERE timestamp >= SYSDATE - 7
ORDER BY timestamp DESC;

这里可以查看数据库账号的登录时间、退出时间以及客户端主机信息。

但这个方法也有局限:

  1. 首先,审计必须提前开启。没有记录的话,事后也补不出来。
  2. 其次,会话审计记录的是登录、退出等事件,不是每条 SQL 的执行明细。

比如查到某个应用账号在 14:00 登录,不能直接认定它就是 14:30 执行慢 SQL 的那个会话,所以这个视图可以作为辅助证据,但不能只凭它就下结论。

五、查出来是应用服务器 IP,怎么办?

前面几种方法,主要是在数据库侧找线索。

但如果应用通过连接池访问 Oracle,就会遇到另一个问题。

假设业务架构是这样的:

用户浏览器
192.168.10.50
      |
      v
Nginx / 网关
10.10.10.10
      |
      v
Java 应用服务器
10.245.40.123
      |
      v
Oracle 连接池
      |
      v
Oracle 数据库
10.245.40.113

在数据库服务器上通过 netstat 查询,得到的 IP 大概率是 10.245.40.123,这本身没有查错。

因为数据库确实是和 Java 应用服务器建立的连接,浏览器并没有直接访问 Oracle,如果业务想知道究竟是哪个用户、哪台终端发起了请求,仅靠 Oracle 侧的会话信息通常就不够了。

需要继续查应用日志、网关访问日志,必要时还要结合 NAT 或代理设备的连接日志。这里最麻烦的其实不是 IP,而是如何把一次业务请求和一条数据库 SQL 对应起来。

比如同一台应用服务器上有几十个业务接口,所有接口都使用同一个数据库账号和连接池,Oracle 看到的可能都是:

USERNAME : MES
MACHINE  : APP-SERVER-01
PROGRAM  : JDBC Thin Client

这种情况下,即使找到了应用服务器 IP,也不知道具体是哪个接口执行的 SQL。

六、能不能让 Oracle 直接记录业务用户?

关于这个问题,我在 Ask TOM 上找到过类似讨论。

当时有人问,WebLogic 使用连接池之后,数据库怎么知道具体是哪个应用用户执行了操作?

Oracle 给出的思路是,让应用在使用数据库连接时,主动设置会话标识。

例如:

BEGIN
  DBMS_SESSION.SET_IDENTIFIER('user_10001');
END;
/

设置后,在数据库里可以查询:

SELECT sid,
       serial#,
       username,
       machine,
       program,
       client_identifier
FROM v$session
WHERE client_identifier = 'user_10001';

这样至少能把数据库会话和应用业务用户关联起来。

如果还想知道具体是哪个接口,也可以使用 DBMS_APPLICATION_INFO 设置模块和操作名称:

BEGIN
  DBMS_APPLICATION_INFO.SET_MODULE(
    module_name => 'MES-API',
    action_name => 'queryBatch'
  );

  DBMS_APPLICATION_INFO.SET_CLIENT_INFO(
    client_info => 'request=abc123'
  );
END;
/

以后再排查 SQL,就不只是看到一个 JDBC Thin Client,而是有机会知道它来自哪个业务模块、哪个接口。

当然,应用侧还需要记录请求 ID、用户标识、终端来源等信息,才能继续向前追踪。

连接池还有个细节需要注意:一个数据库连接可能先后被多个业务请求使用。

因此应用每次获取连接时,都要设置当前请求的信息,使用完后还要清理,不能把上一个用户的标识留给下一个请求。

这件事最好在应用开发阶段就考虑进去。

等慢 SQL 已经发生、会话也断开了,再想办法补这些信息,通常就比较困难了。

七、几种场景怎么选?

前面查了一圈,我整理了一份简单的对照表,以后遇到类似问题可以直接参考。

场景 排查方式
SQL 正在执行 VSESSION→VSESSION → VPROCESS → netstat/ss
SQL 已结束,会话还在 ASH + 当前会话
SQL 已结束,会话已断开 ASH / AWR,查历史 MACHINE
历史 ASH 没有记录 Listener 日志、会话审计
查到的是应用服务器 应用日志、请求 ID、连接池上下文
经过 NAT、代理 网关或网络设备日志
所有历史记录都没有 无法保证还原,只能完善后续记录

最后

这次查完,最大的感受是,Oracle 能查到多少,取决于当时留下了多少信息。

  1. 如果 SQL 还在执行,找到 SID、SPID,再到操作系统上查连接,通常比较直接。
  2. 如果 SQL 已经结束,就得看看 ASH、AWR 有没有采样,监听日志、审计记录是否还在。

如果数据库前面还有连接池,那就不只是 DBA 自己能解决的问题了,需要应用配合,把业务用户、接口和请求信息传递到数据库会话里。

有些情况确实能追溯,有些情况只能找到应用服务器,还有些情况,历史信息已经丢失,根本无法准确还原。

这也是为什么我觉得,除了日常监控 SQL 性能,生产系统最好还能把 SQL 和具体业务请求关联起来。否则遇到慢 SQL,DBA 查到了执行计划,也找到了应用服务器,最后业务问一句“到底是谁触发的”,可能还是答不上来。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。