Oracle 慢 SQL 已经结束了,还能查到客户端 IP 吗?
前言
上一篇文章分享了一个排查 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;
这里可以查看数据库账号的登录时间、退出时间以及客户端主机信息。
但这个方法也有局限:
- 首先,审计必须提前开启。没有记录的话,事后也补不出来。
- 其次,会话审计记录的是登录、退出等事件,不是每条 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 正在执行 | VPROCESS → netstat/ss |
| SQL 已结束,会话还在 | ASH + 当前会话 |
| SQL 已结束,会话已断开 | ASH / AWR,查历史 MACHINE |
| 历史 ASH 没有记录 | Listener 日志、会话审计 |
| 查到的是应用服务器 | 应用日志、请求 ID、连接池上下文 |
| 经过 NAT、代理 | 网关或网络设备日志 |
| 所有历史记录都没有 | 无法保证还原,只能完善后续记录 |
最后
这次查完,最大的感受是,Oracle 能查到多少,取决于当时留下了多少信息。
- 如果 SQL 还在执行,找到 SID、SPID,再到操作系统上查连接,通常比较直接。
- 如果 SQL 已经结束,就得看看 ASH、AWR 有没有采样,监听日志、审计记录是否还在。
如果数据库前面还有连接池,那就不只是 DBA 自己能解决的问题了,需要应用配合,把业务用户、接口和请求信息传递到数据库会话里。
有些情况确实能追溯,有些情况只能找到应用服务器,还有些情况,历史信息已经丢失,根本无法准确还原。
这也是为什么我觉得,除了日常监控 SQL 性能,生产系统最好还能把 SQL 和具体业务请求关联起来。否则遇到慢 SQL,DBA 查到了执行计划,也找到了应用服务器,最后业务问一句“到底是谁触发的”,可能还是答不上来。
- 点赞
- 收藏
- 关注作者
评论(0)