Oracle连接超时参数详解:从JDBC到监听器的排查指南

Oracle连接超时参数详解:从JDBC到监听器的排查指南 半夜两点被电话叫醒应用那边说连不上数据库报错ORA-12170: TNS:Connect timeout occurred。我第一反应是监听挂了结果lsnrctl status一看监听活得好好的数据库也开着。折腾半天最后发现罪魁祸首是Oracle连接超时参数没配好——不是数据库挂了是连接建立阶段在网络层超时了。那次之后我把Oracle这一堆跟超时相关的参数彻底捋了一遍发现里面门道不少而且特别容易混淆。如果你也维护Oracle不管是11g还是19c甚至云上的RDS这篇内容应该能帮你少踩几个坑。我尽量把连接建立阶段的超时、会话空闲超时、死连接检测这几个层面的参数讲清楚还会带上排查思路和典型报错对照。1. 先把Oracle连接超时的链路搞清楚很多人一提到Oracle连接超时就以为是一个参数的事其实不是。一条连接从客户端发起到真正能跑SQL中间要经过客户端TCP握手、监听器接收、服务端进程创建、认证和会话建立这几个环节。超时可能发生在任何一个环节配置参数也散落在不同的文件里。1.1 一条连接经历的几个阶段我用一个最简单的JDBC连接场景来拆解客户端通过jdbc:oracle:thin://host:1521/orcl发起连接首先做TCP三次握手这个阶段由操作系统TCP层管Oracle自身一般不插手。TCP建立后客户端把数据包发给监听器listener监听器在listener.ora里登记的端口上接收请求这个阶段受listener.ora里的CONNECT_TIMEOUT参数影响。监听器收到请求后根据连接描述符找到对应服务名再派生一个专用服务进程dedicated server process或交给共享服务器处理。如果这时数据库负载很高、processes满了进程创建就会变慢容易触发SQLNET.INBOUND_CONNECT_TIMEOUT。服务进程起来后客户端和服务端之间还要做版本协商、认证最后才建立会话。这期间任一方长时间不响应客户端侧的oracle.net.CONNECT_TIMEOUT就会报错。1.2 参数虽然多按层面分只有四类我整理了一下Oracle连接超时相关参数可以按作用位置分成四层层面主要参数或配置作用位置客户端网络层oracle.net.CONNECT_TIMEOUT、oracle.jdbc.ReadTimeoutJDBC、OCI客户端配置客户端sqlnet.oraSQLNET.OUTBOUND_CONNECT_TIMEOUT客户端到服务端的TCP建立阶段监听器层CONNECT_TIMEOUT_listener_namelistener.ora服务端sqlnet.oraSQLNET.INBOUND_CONNECT_TIMEOUT、SQLNET.EXPIRE_TIME服务端接收连接与空闲检测会话层IDLE_TIME、CONNECT_TIME数据库Profile每一层管一段别指望只要设了JDBC的ReadTimeout就能解决监听器的接收超时问题。实际排查时要根据报错信息判断丢在哪一段。2. 服务端和监听器最容易背锅的两个超时参数先说不容易踩坑但问题最隐蔽的SQLNET.INBOUND_CONNECT_TIMEOUT。它在服务端的sqlnet.ora里默认是60秒不同版本有差异11g以后默认60。这个参数的意思是从客户端连接请求到达监听器到客户端完成认证和信息交换的最长时间。超了服务端会主动断开连接客户端往往看到ORA-3136: WARNING: inbound connection timed out。2.1 SQLNET.INBOUND_CONNECT_TIMEOUT不是越大越好这个参数很多DBA会犯一个毛病——遇到报错就把它调大调到300秒、600秒。其实这是一个防御性参数设计初衷是防止客户端连上后长时间不完成握手占着监听器的槽位不放。比如有客户端程序写了错误的连接方式或者网络里有设备在悄悄探测端口连上后不发数据如果这个参数太小正常客户端在慢网络上可能来不及完成连接但如果调得过大恶意连接或故障连接会长时间占用进程和会话槽位。我踩过的一个坑是这样的某次做数据库迁移新机房网络策略比较严客户端到数据库中间还有一层代理TCP握手没问题但代理转发数据有延迟。应用连库时频繁报ORA-3136我一开始把SQLNET.INBOUND_CONNECT_TIMEOUT从60改成120结果还是偶发报错。后来用抓包发现连接请求到达监听器后客户端要隔很久才发下一步数据包不是服务端慢是中间代理缓冲导致。最后在代理上关闭了TCP的延迟ACK并把INBOUND_CONNECT_TIMEOUT保持60秒问题就消失了。实操建议这个参数默认值在绝大多数场景够用除非你的网络确实有代理转换、跨地域专线等场景否则不建议动。2.2 listener.ora里的CONNECT_TIMEOUT跟版本和命名有关监听器还有一个自己的超时参数写法是CONNECT_TIMEOUT_listener_name默认值一般是10秒10g时代有的平台默认0也就是不限制。它管的是监听器在接收客户端连接请求时允许客户端在完成连接之前的最长等待时间比SQLNET.INBOUND_CONNECT_TIMEOUT更靠前。如果客户端已经TCP连上了监听器但迟迟没有发出完整的连接描述符监听器会在超时后主动断开。这里有个容易被忽略的点改完listener.ora后需要lsnrctl reload或lsnrctl stop/start才生效而SQLNET.INBOUND_CONNECT_TIMEOUT的修改不需要重启监听器新的连接会立即使用新值。动手前务必要区分当前正在调的是哪个参数否则容易白忙活。2.3 两个参数之间的关系和典型配置这两个参数其实是串联关系客户端请求先经过监听器的CONNECT_TIMEOUT然后进入服务端的SQLNET.INBOUND_CONNECT_TIMEOUT。实操中我通常这样配监听器的CONNECT_TIMEOUT设置10秒左右防止TCP连接占着端口不干活服务端SQLNET.INBOUND_CONNECT_TIMEOUT保持默认60秒或者根据跨专线实际情况调整到90~120秒。注意如果数据库并发很高派生服务进程比较慢可以适当观察告警日志里是否有WARNING: inbound connection timed out有的话结合抓包确认是网络问题还是进程创建瓶颈不要盲目调大超时。3. 应用侧JDBC参数真正决定你业务报错的几个值服务端的超时参数再合理如果应用侧没配好业务该报错还是报错。JDBC驱动里跟超时相关的有三个经常被混用的参数oracle.net.CONNECT_TIMEOUT、oracle.jdbc.ReadTimeout、oracle.net.READ_TIMEOUT。3.1 连接建立超时和读取超时是两回事oracle.net.CONNECT_TIMEOUT控制的是建立连接阶段的超时单位是毫秒默认值在某些版本里是0无限等待。它处理的是从客户端发起TCP连接到数据库返回“已连接”这个阶段。如果数据库IP不通、端口不通、监听异常这个参数直接决定你的应用是等几秒报错还是挂半天。oracle.jdbc.ReadTimeout和oracle.net.READ_TIMEOUT控制的是连接建立后客户端等待数据库返回数据的超时时间。它们的区别是oracle.jdbc.ReadTimeout是旧的、只作用于thin驱动而oracle.net.READ_TIMEOUT是11.2版本以后引入的对thin和OCI都生效而且它覆盖了oracle.jdbc.ReadTimeout的语义。如果你两个都配了以oracle.net.READ_TIMEOUT为准。3.2 一个慢SQL引发的“思考题”我遇到过这样一个案例运维同事觉得数据库偶尔“卡住”了就在JDBC URL里加了oracle.jdbc.ReadTimeout3000想做到3秒读超时。结果上线后一批正常的夜维报表在数据量大的时候全挂报ORA-17002或者Socket read timed out。查了半天发现报表SQL本身要跑20秒但ReadTimeout设成了3秒等于把正常慢查询全部误杀。所以配置读取超时前先统计一下业务里最慢SQL的执行时间把超时时间设置成“合理慢查询上限”而不是凭感觉设一个很小的值。如果是连接池场景还要考虑连接池获取连接的超时那是连接池自身的参数比如HikariCP的connectionTimeout别跟JDBC参数混在一起。3.3 一个可靠的JDBC参数配置示例我现在的项目里用的是这样的配置供参考jdbc:oracle:thin:(DESCRIPTION(ADDRESS(PROTOCOLTCP)(HOSTdbhost)(PORT1521))(CONNECT_DATA(SERVICE_NAMEorcl))) ?oracle.net.CONNECT_TIMEOUT10000oracle.net.READ_TIMEOUT60000注意在JDBC URL里参数用连接oracle.net.CONNECT_TIMEOUT是毫秒10秒建立连接。oracle.net.READ_TIMEOUT是60秒。如果走的是Properties方式则是Properties props new Properties(); props.setProperty(user, scott); props.setProperty(password, tiger); props.setProperty(oracle.net.CONNECT_TIMEOUT, 10000); props.setProperty(oracle.net.READ_TIMEOUT, 60000); Connection conn DriverManager.getConnection(url, props);如果业务里有批处理、大查询建议把READ_TIMEOUT设置得更宽松一些或者干脆不设置只靠数据库侧的IDLE_TIME等参数兜底。3.4 OCI连接还有一个容易忽略的OUTBOUND参数如果你用的是OCI模式比如通过sqlplus、Pro*C、某些中间件sqlnet.ora里还有SQLNET.OUTBOUND_CONNECT_TIMEOUT单位是秒默认是0也就是无限等待。这个参数限制的是客户端到服务端TCP连接建立的时间。thin模式JDBC不走这个参数但sqlplus会走。有一次用户反应sqlplus连远程数据库时长时间卡住不报错网络又不通问题就出在这个参数没设。加上SQLNET.OUTBOUND_CONNECT_TIMEOUT10之后网络不通时sqlplus能在10秒内快速失败而不是让DBA干等。4. 会话空闲与死连接检测连接池里的隐形杀手连接超时不只在建立连接时发生。连接建立后如果长时间空闲或中间网络设备把连接静默断掉应用感知不到继续用这条“死连接”就会报错。这时候就看SQLNET.EXPIRE_TIME和数据库Profile的IDLE_TIME了。4.1 SQLNET.EXPIRE_TIME 是做“心跳检测”的SQLNET.EXPIRE_TIME写在服务端sqlnet.ora单位是分钟默认0不启用。它的原理是服务端每隔设定时间向客户端发送一个探测包如果连续多次没有收到响应服务端就认为这个连接已经死了回收对应的服务进程。注意这个探测间隔比较长默认配置下探测包发送频率很低比如EXPIRE_TIME10每10分钟才探测一次所以它解决不了“中间网络设备几分钟就空闲超时”的场景。如果NAT网关、防火墙或负载均衡的会话空闲超时是300秒那你把EXPIRE_TIME设为30分钟就完全没意义死连接照样会被网关先掐断。我之前的经验是EXPIRE_TIME至少要小于网络设备空闲超时的一半。举个例子公司的防火墙对空闲TCP会话有15分钟的超时我就把SQLNET.EXPIRE_TIME5这样数据库每5分钟发一个探测包让连接一直处于“活跃”状态防火墙就不会把连接清掉。4.2 Profile里的IDLE_TIME和CONNECT_TIME除了网络层面的死连接检测数据库Profile可以限制会话的空闲时间和总连接时间ALTER PROFILE app_user LIMIT IDLE_TIME 30 CONNECT_TIME 480 RESOURCE_LIMIT TRUE;IDLE_TIME是指一个会话空闲超过30分钟后被断开。执行这个变更前先确认RESOURCE_LIMIT是TRUE否则Profile里的很多资源限制不生效。CONNECT_TIME是指会话从连接到断开的总时长上限主要是为了防止某些应用连接泄漏、长期不释放。这个功能在生产环境要慎用。曾有开发让我帮他们把IDLE_TIME设为10分钟结果一个Java应用因为连接池里的连接空闲超过10分钟被数据库狠心掐断而应用连接池的验证查询validation query没配好应用一直在往已断掉的连接上发SQL报错报了一大片。后来我把IDLE_TIME调到60分钟同时在连接池里配了SELECT 1 FROM DUAL作为每次借出连接前的校验问题才消停。4.3 Linux内核层的TCP超时参数Oracle本身管不了的那段客户端和服务端之间的TCP连接在操作系统层面还有一套超时机制。以Linux为例sysctl net.ipv4.tcp_keepalive_time sysctl net.ipv4.tcp_keepalive_intvl sysctl net.ipv4.tcp_keepalive_probestcp_keepalive_time默认7200秒2小时也就是说TCP层的keepalive探测包默认要2小时才发一次。这对于很多网络设备来说太慢了所以上面提到的SQLNET.EXPIRE_TIME才显得重要——它比TCP keepalive更灵敏。但如果网络条件允许也可以调Linux内核参数来配合sysctl -w net.ipv4.tcp_keepalive_time300 sysctl -w net.ipv4.tcp_keepalive_intvl30 sysctl -w net.ipv4.tcp_keepalive_probes3这样TCP层在300秒没有数据时会发探测包30秒一次连续3次没响应就判定连接断开。注意这会影响整个操作系统上的所有TCP连接不只是Oracle改动前要评估对其他业务的影响。新版本内核还可以用TCP_USER_TIMEOUT单位是毫秒可以在发送数据后多久内没收到ACK就报错比keepalive配置更精准不过这个一般是在应用层通过socket选项设置Oracle本身不暴露这个配置。5. 常见报错与排查直接对号入座前面讲了参数原理这一节放点实战东西。我列一个常见Oracle连接超时报错对照表然后说一次完整排查套路。5.1 报错速查表报错信息大概率原因优先排查/调整方向ORA-12170: TNS:Connect timeout occurred网络不通、监听器不响应、防火墙丢包确认IP和端口连通性检查listener是否在监听检查监听器CONNECT_TIMEOUTORA-12547: TNS:lost contact客户端与服务器之间的连接被弄断可能是网络不稳定、防火墙会话超时检查SQLNET.OUTBOUND_CONNECT_TIMEOUT抓包看哪一端先发了RSTORA-3136: WARNING: inbound connection timed out服务端接收连接超时查看SQLNET.INBOUND_CONNECT_TIMEOUT确认进程创建是否过慢ORA-28547: connection to server failed, probable Oracle Net admin error服务名或监听配置不匹配常见于连接描述符写错检查tnsnames.ora连接描述符、listener.ora服务注册Socket read timed outJDBC读取超时检查oracle.net.READ_TIMEOUT确认是否太小检查服务端是否有慢SQLORA-02396: exceeded maximum idle time会话空闲超过Profile限制检查Profile的IDLE_TIME设置优化应用连接池的空闲管理这里要提醒一下ORA-12170不一定就是网络问题。有一次我发现数据库服务器CPU打满监听器收到了连接请求但派发不出来客户端也报ORA-12170。所以报错只能做线索不要当成结论。5.2 完整排查套路从一端到另一端我在处理连接超时时基本按下面这个顺序做第一步确认网络连通性。先telnet 数据库IP 1521看端口通不通。如果telnet就卡住说明问题在网络层。此时可以顺带ping一下测一下丢包率但很多防火墙禁ping所以ping不通不代表TCP不通。第二步看监听器状态。执行lsnrctl services和lsnrctl status确认服务名是否正确注册。如果service_name没注册客户端连接会直接超时或报ORA-12514。第三步看告警日志。连接超时类问题数据库的alert_log往往有记录。grep -i timeout $ORACLE_BASE/diag/rdbms/*/trace/alert_*.log。如果有WARNING: inbound connection timed out那就是SQLNET.INBOUND_CONNECT_TIMEOUT或监听器超时被触发。第四步抓包确认断开方。用tcpdump -i eth0 port 1521抓包如果客户端在连接建立后发了一个RST包说明是客户端等不及主动断开如果服务端先发RST说明是服务端或中间设备做了拦截。这个步骤可以快速把锅甩到正确的一方。第五步复查配置文件。show parameter resource_limit确认是否开启检查sqlnet.ora里SQLNET.EXPIRE_TIME、SQLNET.INBOUND_CONNECT_TIMEOUT检查listener.ora里CONNECT_TIMEOUT_listener_name检查应用侧JDBC URL里加的超时参数。把每个参数当前值列出来对照超时时间线看哪个值跟故障时间点最吻合。5.3 一个典型的排查案例之前处理过一个批量导入程序报“ORA-12170”但出现时间毫无规律。我先telnet数据库1521端口秒通lsnrctl services正常告警日志里没有任何超时记录。然后抓包发现客户端发出的TCP SYN包到了数据库数据库回了SYN-ACK但客户端那边没有继续发ACK。再往下看发现数据库返回的SYN-ACK之后客户端几秒后直接发了RST。这个现象说明客户端某些线程在TCP层面就放弃连接了根本没走到Oracle监听器。后来查到是Java应用里的oracle.net.CONNECT_TIMEOUT被设成了2000毫秒而那条链路因为中间有网关做安全检查TCP三次握手本来就需要2~3秒。把连接超时调成10秒就再没报过错。所以连接超时排查有一个核心心法先确定断在哪一段再决定调哪个参数。5.4 参数修改后的验证方式修改完参数建议分两步验证。第一步用sqlplus模拟客户端的普通连接是否正常第二步模拟超时故障比如临时用iptables把1521端口的包丢掉看看报错是否能在预期时间内出现。# 模拟端口丢包注意实际生产环境谨慎操作建议在测试环境验证 iptables -A INPUT -p tcp --dport 1521 -j DROP sqlplus scott/tigerhost:1521/orcl # 观察是否在预期时间后报出超时错误 iptables -D INPUT -p tcp --dport 1521 -j DROP这样一来参数是不是真的生效一测便知比改完不管强得多。6. Oracle 11g与19c在超时参数上的小差异如果你还在维护11g可能会遇到一些跟新版本不太一样的默认行为。6.1 11g与19c默认值不完全一样在Oracle 11g中SQLNET.INBOUND_CONNECT_TIMEOUT默认已经是60秒但有些操作系统平台上的listener.ora默认没有显式写CONNECT_TIMEOUT_listener_name这时候监听器是否有限制取决于版本和平台有的默认不限制。而在19c中监听器默认会有一个合理的超时值整体更安全。JDBC驱动的差别更大。老版本的Oracle JDBC驱动比如11.2.0.x对oracle.net.READ_TIMEOUT支持不完整用的时候需要把驱动升级到较新的版本否则配了也可能不生效。6.2 升级到19c后注意连接参数兼容19c的服务器对老版本客户端的兼容性整体还行但如果客户端JDBC驱动太老10g时代连接服务端时可能出现认证参数协商超时。这时候不能只调服务端超时还要考虑把客户端驱动升级到至少11.2.0.4以上版本。升级驱动的经验是先在一台测试服务器验证再看应用服务器的JDK版本是否兼容。Oracle 19c驱动要求JDK8以上有些人还在用JDK6跑老应用驱动升不上去这种情况只能保持旧驱动并调大连接超时参数来缓解。7. 到底该怎么设计一套合理的超时配置写到这里我把配置Oracle连接超时参数的思路做个收拢方便你对照自己环境去调整。7.1 先分层不盲目调大设计超时参数之前先把链路每一层的期望值写清楚。我的习惯是客户端TCP连接建立5~10秒。如果超过10秒还没建上连接多半是网络配置有问题靠调超时治标不治本。监听器接收连接描述符10秒左右跟客户端建立超时匹配。服务端完成认证与进程创建30~60秒。如果数据库负载正常但超过60秒说明连接风暴或进程创建瓶颈要考虑的是容量问题不是调超时。读取数据超时按业务最慢SQL的P95时间乘以1.5~2倍并且要区分报表、批处理和在线交易。空闲会话按应用连接池的空闲策略和网络设备超时来定原则是SQLNET.EXPIRE_TIME小于网络设备空闲超时IDLE_TIME大于连接池最大空闲时间否则连接池里的连接会被数据库先干掉。7.2 一个相对稳妥的参考配置这个配置适合大多数中型OLTP系统可以直接抄作业但要根据业务微调# 服务端 sqlnet.ora SQLNET.INBOUND_CONNECT_TIMEOUT60 SQLNET.EXPIRE_TIME10# 监听器 listener.ora CONNECT_TIMEOUT_LISTENER10-- 数据库Profile ALTER PROFILE app_profile LIMIT IDLE_TIME 60 CONNECT_TIME 480 RESOURCE_LIMIT TRUE;// JDBC URL参数 oracle.net.CONNECT_TIMEOUT10000 oracle.net.READ_TIMEOUT60000这套配置的思路是连接建立阶段的超时控制在10~60秒内空闲检测每10分钟一次会话最长8小时JDBC读取超时60秒。如果业务里确实有超过60秒的大查询把READ_TIMEOUT适当放大或者对大查询走单独的连接配置。7.3 配置之后需要持续观察参数改完不是结束还要关注两件事。一是告警日志里是否还有超时相关警告二是在数据库里查询当前会话的空闲时间分布判断IDLE_TIME是否合理SELECT username, status, count(*), ROUND(MAX(last_call_et)/60, 1) AS max_idle_min FROM v$session WHERE type USER GROUP BY username, status ORDER BY 4 DESC;如果很多会话空闲超过几小时说明应用连接池配置有问题光靠数据库断开也不能根治。我个人在实际操作中的体会是Oracle连接超时参数看起来零散但掌握了“一条链路四层参数”的框架后大部分超时问题都能快速定位。别急着调大数值更别所有超时都设成一样大的值先搞清楚当前故障断在哪一段再有针对性地调整。最后留一句经验之谈遇到连接超时先抓包再改参数能少走很多弯路。