Oracle Pro*C 程序卡死排查:strace 抓不到的「库内 spin」用 gdb 定位
一句话场景:生产环境一个 Oracle Pro*C 数据加载程序卡死不退出,CPU 持续 97%,strace 却抓不到主线程在干什么;新起的同程序同 SQL 又能正常完成。本文记录从 strace 到 gdb 的定位过程,以及一路踩过的误判坑。
背景
某 Oracle Pro*C 数据加载程序(sqlcxt 执行 SELECT 拉数据,产索引文件)出现进程卡死:CPU 持续 97%,90 分钟不退出。但新起的同程序同 SQL 能正常完成。需要定位根因。
一、strace 看不到主线程在干嘛
strace -p <PID> -f
只看到子线程 nanosleep(1秒) 心跳,主线程一个 syscall 都没有。但 ps 显示主线程 CPU 97%。
关键认知:进程卡死 + strace 看不到 syscall + CPU 高 = 主线程在库内 spin(某个
.so内部忙循环,不调 Linux syscall)。strace 抓不到,因为 strace 只抓系统调用。
这一步差点把我带偏——一开始以为 strace 没输出 = 进程在等 IO(CPU 应该低),但 ps 的 97% CPU 矛盾。意识到是「库内 spin」后才转用 gdb。
二、gdb 调用栈定位
gdb -p <PID> -batch -ex "thread apply all bt"
主线程调用栈:
ttcclr / ttcfopr / ttcdrv ← Oracle TTC (Two-Task Common) 层
nioqwa ← Oracle 网络 I/O 等待
upirtrc ← UPI retry(重试,spin 不 sleep → CPU 97%)
kpurcsc / kpufch0 / kpufch ← Oracle fetch 行
sqlcucFetch / sqlall / sqlsel / sqlnst / sqlcxt ← Pro*C 执行 SQL + fetch
定位:程序卡在 Oracle Pro*C fetch(sqlcxt → kpufch),Oracle 客户端库 libclntsh 在 upirtrc(UPI retry)spin 重试不放弃,CPU 97%。
三、根因逐步收窄
每条观察推翻一个推测,最终才逼近真相:
| 观察 | 推翻的推测 |
|---|---|
| 新起同程序同 SQL 能成功 | SQL 性能 / Oracle 端普遍问题 |
| 非并发(每次单独起) | 并发锁竞争 |
| 同时段有成功有卡 | 时段环境问题 |
| 两 IP 无防火墙同网段 | NAT/防火墙 idle 超时断 TCP |
| TCP ESTABLISHED 超 2 小时 | 连接死 / session 死 / OOM kill |
TCP ESTABLISHED 2h+ 是决定性证据:如果连接死了 / session 被 kill / OOM,TCP 会断开(RST/FIN),不会保持 ESTABLISHED 2 小时。说明 Oracle 端 TCP 活着,问题在应用层。
最终收窄:Oracle 应用层 session 不回 fetch 响应(session ACTIVE 卡在某 wait event,或 INACTIVE 双方互等),TCP 层活着,但 19c 客户端 libclntsh 无读超时,spin 不放弃。
四、19c vs 11g 客户端行为差异
同程序在 11g 客户端不卡(没设 sqlnet.ora 超时也正常):
- 11g libclntsh:遇连接异常较快报
ORA-03113/03114退出 - 19c libclntsh:改了
upirtrc重试逻辑,spin 不放弃
这是 Oracle 客户端版本行为差异,不是配置问题。
五、根因与放大因素
直接原因:Oracle 应用层 session 不回 fetch 响应(hang 或互等)
三个放大因素叠加,导致 spin 长达 90 分钟:
- 19c libclntsh
upirtrcspin 重试不放弃 sqlnet.ora RECV_TIMEOUT默认 0(无限读等待)- OS TCP keepalive 默认 7200 秒(2 小时,期间客户端不知道 session 异常)
六、处置与预防
应急处置:kill -9 卡死进程(libclntsh spin 不自愈,救不了)
治本预防——sqlnet.ora 设超时:
SQLNET.RECV_TIMEOUT = 60 # fetch 读超时 60 秒报错退出(核心,打破 spin)
SQLNET.SEND_TIMEOUT = 60
SQLNET.EXPIRE_TIME = 10 # DCD 死连接探测
下次卡时立即查 v$session(别急着 kill),定位具体 hang:
SELECT s.sid, s.serial#, s.status, s.event, s.sql_id,
s.blocking_session, s.seconds_in_wait, s.wait_class, s.state,
s.machine, s.program, p.spid
FROM v$session s, v$process p
WHERE s.paddr = p.addr
AND (s.username = '<USER>' OR s.program LIKE '%<prog>%')
ORDER BY s.seconds_in_wait DESC NULLS LAST;
按 status + event 判断 hang 类型:
| status | event | 含义 |
|---|---|---|
INACTIVE | SQL*Net message from client + seconds_in_wait 巨大 | 双方互等 |
ACTIVE | latch: cache buffers chains / library cache lock | 内部争用 |
ACTIVE | enq: TX row lock + blocking_session 非空 | 被锁 |
七、方法论总结
- 进程卡死 + strace 看不到 syscall + CPU 高 = 库内 spin,第一时间
gdb -p <PID> -batch -ex "thread apply all bt",别在 strace 上耗时间 - 每条观察逐步收窄,避免经验主义误判——本次误判过 busy-loop / 并发锁 / SQL 慢 / 时段环境 / 连接死
- TCP ESTABLISHED 是判断「连接死 vs 应用层 hang」的关键证据
- Oracle 客户端版本(11g vs 19c)对连接异常的处理差异是常见坑

浙公网安备 33010602011771号