
1. WebLogic 连接池下 ORA-01000 是怎么冒出来的ORA-01000: maximum open cursors exceeded这个报错字面意思是超出最多允许打开的游标数。它是什么简单说Oracle 给每个会话设了一个上限规定一个会话同一时刻最多能同时打开多少个游标cursor。游标你可以理解成数据库为一条 SQL 语句准备的执行手柄只要语句还在执行、结果集还没读完、或者对象没被显式关闭这个手柄就一直占着名额。一旦某个会话占用的游标数顶到了OPEN_CURSORS参数设定的天花板再想开新游标Oracle 就直接抛 ORA-01000。它能做什么、会带来什么后果在 WebLogic 这种应用服务器场景里报错不会只停在数据库层。连接池里的物理连接是复用的某个连接对应的数据库会话游标被占满后这个连接后续执行任何 SQL 都会失败异常会一路冒泡到应用层最终你看到的是java.sql.SQLException: ORA-01000: maximum open cursors exceeded适合谁来读这篇如果你正在维护跑在 WebLogic 上的 Java EE 应用最近频繁在日志里刷到 ORA-01000或者你是个刚接手 Oracle WebLogic 组合的 DBA/后端想搞清楚到底是数据库参数太小还是代码在漏游标那这篇就是给你写的。我试过在几个生产环境里追这类问题最坑的地方在于报错现场和真正的原因往往隔了好几层光看应用日志根本定位不到。核心矛盾其实就两类根因必须先分清楚否则调参就是瞎调第一类是硬解析激增。SQL 文本每次都不一样比如拼字符串拼出来的条件、没绑变量Oracle 每次都要重新解析、生成新的游标短时间内游标数暴涨。这类问题的特征是v$open_cursor里能看到大量结构相似但文本不同的 SQL。第二类是游标未关闭。代码里Statement、ResultSet用完没关或者 WebLogic 的语句缓存把预处理语句攒着不放导致游标只增不减。这类问题的特征是某个会话的游标数持续单调上涨重启连接池能暂时缓解但过一阵又满。这两类的排查路径和修复手段完全不同。下面我会从v$open_cursor和会话游标分布入手把定位动作、可复制的调优脚本、连接池配置片段、以及验证游标回收的 SQL 全部给出来。你跟着做基本能在一轮排查内锁定方向。2. 用 v$open_cursor 定位游标分布与 OPEN_CURSORS 现状排查 ORA-01000第一步永远是先看数据库当前的OPEN_CURSORS到底设了多少再看是哪个会话在疯狂占游标。这两个动作都靠v$视图完成需要 DBA 权限或者被授权 select 这些视图。先确认参数值。Oracle 用初始化参数OPEN_CURSORS指定单会话游标上限默认值只有 50——这个默认值对 WebLogic 这种连接池系统来说小得离谱基本一上量就爆。查当前值SQL show parameter open_cursors; NAME TYPE VALUE ------------------------------------ ----------- ------ open_cursors integer 1000如果这里显示 300 甚至 50那先别急着改代码参数本身就不够用。把OPEN_CURSORS设大不会增加系统开销即使实际用不到那么多也不会白白占内存所以该调就调。接着看游标都堆在哪些会话上。下面这条查询按降序显示指定用户每个会话打开的游标数user_name要换成你连接池使用的数据库账号SQL select o.sid, s.osuser, s.machine, count(*) num_curs 2 from v$open_cursor o, v$session s 3 where o.user_name APPUSER and o.sid s.sid 4 group by o.sid, s.osuser, s.machine 5 order by num_curs desc; SID OSUSER MACHINE NUM_CURS ---------- ----------- ---------- -------- 217 weblogic app-node1 1000 96 weblogic app-node2 10 411 weblogic app-node3 10 50 test dev-box 9结果里那个NUM_CURS 1000的 SID 就是重灾区而且MACHINE直接告诉你是哪台 WebLogic 节点。注意一个细节v$open_cursor跟踪的是会话里 PARSED 和 NOT CLOSED 的动态游标也就是用dbms_sql.open_cursor()打开的那种。它不会跟踪未经解析但已打开的游标。正常应用里动态游标用得不多所以这个视图对绝大多数 WebLogic 场景够用。拿到问题 SID 后下一步是看这个会话到底在执行什么 SQL。用上一步找到的 SID 去关联v$sqlSQL select q.sql_text 2 from v$open_cursor o, v$sql q 3 where q.hash_value o.hash_value and o.sid 217; SQL_TEXT -------------------------------------------------------------- select * from empdemo where empid212 select * from empdemo where empid321 select * from empdemo where empid947 select * from empdemo where empid527 ...这一步是分水岭。如果你看到的是大量文本几乎一样、只有字面量不同的 SQL比如上面这种empid212、empid321一条条变那基本可以判定是硬解析问题——SQL 没绑变量每次都是新语句游标自然越开越多。反过来如果看到的是少数几条 SQL 反复出现、数量却一直涨那更可能是游标没关或者语句缓存把句柄攒住了。再补一个观察游标随时间变化的动作隔几十秒跑两次同一条统计看某个 SID 的num_curs是不是单调上涨SQL select sid, count(*) num_curs 2 from v$open_cursor 3 where user_name APPUSER 4 group by sid 5 order by num_curs desc;如果两次之间某个 SID 从 200 涨到 400 再到 600且业务量没明显变化那就是泄漏型如果是在业务高峰才冲高、低谷回落那更偏向硬解析或缓存配置问题。把这两类区分开后面的修复才不会南辕北辙。3. 可复制的 OPEN_CURSORS 调整与 WebLogic 连接池配置定位清楚之后进入修复环节。这一节给的都是可以直接复制粘贴的配置片段路径和参数名按 Oracle 与 WebLogic 的实际约定来。先调数据库参数。OPEN_CURSORS是静态参数改完要重启实例或者用scopespfile改完下次启动生效。生产环境建议直接改 spfileSQL alter system set open_cursors 2000 scope spfile; System altered.改完重启实例后验证SQL show parameter open_cursors; NAME TYPE VALUE ------------------------------------ ----------- ------ open_cursors integer 2000设多大合适没有万能值。经验做法是先按当前峰值会话游标数的 1.5 到 2 倍来设比如你观察到单会话峰值到过 900那就设 2000。设大不增加开销所以宁可宽一点别抠。然后是 WebLogic 连接池这一侧。语句缓存是 ORA-01000 的高发区因为 WebLogic 把预处理语句和可调用语句缓存起来复用时DBMS 在很多情况下会为每个打开的语句保留游标。缓存越大占的游标越多。不同版本默认值还不一样6.1 默认预处理语句缓存为 07.0 非 XA 和 XA 默认 5/语句8.1 预处理加可调用合计默认 10。如果你最近升级过 WebLogic缓存行为变化很可能就是游标数突然上涨的诱因。在 WebLogic 控制台里连接池的语句缓存大小属性叫Statement Cache Size。要快速验证是不是缓存导致的先把它设成 0 关掉缓存看报错是否消失。如果关掉后不再复现说明就是缓存过大或数据库上限太低二者调一个即可。连接池的关键参数建议这样配以控制台字段名为准参数建议值说明初始容量 Initial Capacity5按实际并发起步最大容量 Maximum Capacity50别盲目设大连接多会话就多语句缓存大小 Statement Cache Size10~50先小后调观察游标测试表名 Test Table NameSQL SELECT 1 FROM DUAL保活检测收缩频率 Shrink Frequency300秒定期回收空闲连接如果你用 WLST 脚本管理连接池配置片段大致长这样# WLST 片段调整连接池语句缓存 cd(/JDBCSystemResources/AppPool/JDBCResource/AppPool/JDBCConnectionPoolParams/AppPool) cmo.setStatementCacheSize(20) cmo.setInitialCapacity(5) cmo.setMaxCapacity(50) cmo.setShrinkFrequencySeconds(300)改完记得激活更改并重启受管服务器。这里有个坑语句缓存调小会牺牲一点性能因为预处理语句复用率下降但换来的是游标可控。生产上我一般先把缓存设成 20 观察一天游标稳定再考虑要不要往上加。另外如果排查发现是 JDBC 驱动本身的游标泄漏比如某些 Oracle XA 驱动版本调参治标不治本。判断方法绕过 WebLogic 连接池直接从驱动拿连接但故意不关闭让它们以数组形式保持打开模拟连接池行为看游标泄漏是否依旧。如果依旧那就是驱动问题考虑换驱动版本或升级。XA 驱动还有个已知点如果数据库里出现大量Select count(*) FROM SYS.DBA_PENDING_TRANSACTIONS这类查询可能是 XA 驱动的游标泄漏需要确认数据库侧已正确授权例如grant select on dba_pending_transactions to public。4. 验证游标回收与复现请求的 SQL 检查动作配置改完不代表问题解决必须验证游标真的能被回收。这一节给的是可复现、可对比的检查动作你照着跑就能确认修复是否生效。第一个动作制造一次业务请求然后立刻观察目标会话的游标数。假设你的连接池账号是APPUSER先记录基线SQL select sid, count(*) num_curs 2 from v$open_cursor 3 where user_name APPUSER 4 group by sid 5 order by num_curs desc;然后触发一次典型业务操作比如调用那个查询 empdemo 的接口再跑一次同样的统计。如果修复到位你会看到游标数在请求结束后回落到接近基线而不是停在高位不动。这就是游标回收的直接证据。第二个动作确认语句缓存是否在正常复用而不是堆积。查缓存命中相关的动态性能视图观察解析次数和游标数的关系SQL select sql_text, count(*) cnt 2 from v$open_cursor 3 where user_name APPUSER 4 group by sql_text 5 having count(*) 5 6 order by cnt desc;如果同一段 SQL 文本的cnt稳定在一个小数值比如等于连接数乘以缓存大小说明缓存复用正常。如果某条 SQL 的cnt持续上涨那要么是没绑变量导致硬解析要么是缓存没释放。第三个动作验证硬解析是否被压下去。查v$sysstat里的解析统计SQL select name, value 2 from v$sysstat 3 where name in (parse count (total), parse count (hard));隔一段时间跑两次算硬解析占比。如果硬解析占比很高说明 SQL 没复用得回到代码层去绑变量。这一步能帮你确认调参和改代码哪个才是真正的解药。第四个动作针对游标泄漏做一次连接池收缩验证。在 WebLogic 控制台对连接池执行 shrink 或 reset然后立刻查游标分布SQL select o.sid, s.machine, count(*) num_curs 2 from v$open_cursor o, v$session s 3 where o.user_name APPUSER and o.sid s.sid 4 group by o.sid, s.machine 5 order by num_curs desc;如果收缩后游标数明显下降说明之前确实有连接把游标攒着没放。这可以作为临时缓解手段但根因还得回到代码或驱动。代码层面的修复原则也要落到验证上。正确做法是在finally块里显式关闭ResultSet、Statement、ConnectionConnection conn null; Statement stmt null; ResultSet rs null; try { conn getConnection(); stmt conn.createStatement(); rs stmt.executeQuery(select * from empdemo); // do work } catch (Exception e) { // handle } finally { try { if (rs ! null) rs.close(); } catch (SQLException e) {} try { if (stmt ! null) stmt.close(); } catch (SQLException e) {} try { if (conn ! null) conn.close(); } catch (SQLException e) {} }要特别警惕那种在循环里反复getConnection()、createStatement()却不逐个关闭的写法。虽然 JDBC 规范说关闭 Connection 会连带关闭 Statement 和 ResultSet但如果你在一个连接上创建了多个 Statement在 Connection 关闭前游标就可能已经超限。所以用完就关别等。5. 本篇常见报错排查401、local proxy failed 与 reading choices排查过程中除了 ORA-01000 本身你还会撞到一些周边报错。这一节把几个高频的对照着讲避免你在错误方向上浪费时间。ORA-01000 反复出现但参数已调大。如果OPEN_CURSORS已经设到 2000 甚至更高报错还在刷那基本可以排除参数问题锁定游标泄漏。回到第 2 节的v$open_cursor按 SID 统计看是不是某个会话单调上涨。是的话重点查代码里未关闭的 JDBC 对象以及语句缓存大小。把缓存设成 0 再压测一轮如果不再复现就是缓存配置问题。local proxy failed类连接错误。这个报错通常出现在应用通过某种本地代理或连接转发访问数据库时代理层没起来或端口不通。它和 ORA-01000 不是一回事但经常一起出现因为游标耗尽后连接被拖垮代理层跟着报错。排查顺序是先确认数据库监听正常lsnrctl status再确认连接池的 URL、端口、服务名配置无误。别一看到连接失败就去调游标方向会跑偏。reading choices相关解析异常。这类报错多见于客户端或驱动在读取服务端返回的选项列表时出错往往和驱动版本、字符集、或者 TNS 配置有关。如果你在换驱动排查游标泄漏的过程中撞到它先回退到稳定版本别在排查主问题的同时引入新变量。401 未授权。如果你在排查时顺带用 API 方式拉取数据库监控或调用模型辅助分析日志可能会遇到 401。这通常是 Key 没带对或过期。以 TaoToken 为例调用模型对话接口时需要在请求头带上正确的 API KeyBase URL 用https://taotoken.net/apiKey 在控制台的 API Keys 页面生成。401 和 ORA-01000 没有因果关系分开处理即可。OAuth 相关报错。如果你用 Claude Code 之类的工具做代码审查、辅助定位游标泄漏点偶尔会遇到 OAuth 授权失败。这类问题检查授权是否过期、回调地址是否匹配即可同样和数据库游标无关别混在一起排查。把这几类报错分开对待是排查效率的关键。ORA-01000 的战场在数据库会话和连接池其他报错各有各的战场混着看只会越查越乱。6. 把排查动作沉淀成日常巡检游标问题解决一次不算完WebLogic Oracle 这套组合只要代码在迭代、连接池在调游标就可能再次失控。所以最后我想说的是把它变成日常动作而不是等报错来了再救火。你可以把第 2 节那条按 SID 统计游标数的 SQL 做成定时巡检每天跑一次记录每个连接池账号的游标峰值。一旦发现某个 SID 的游标数比上周同期明显上涨就提前介入别等它顶到OPEN_CURSORS。同时把parse count (hard)的占比也纳入监控硬解析占比异常升高往往预示着 SQL 写法出了问题。连接池的语句缓存大小也别设完就不管。每次 WebLogic 升级或 JDBC 驱动升级后重新确认一遍缓存默认值有没有变——不同版本差异很大升级引入的缓存行为变化是游标泄漏的常见诱因。升级前先在测试环境压一轮用第 4 节的验证动作确认游标能正常回收再上生产。如果你在排查时需要快速查一些 Oracle 视图字段含义、或者让模型帮你分析一段 JDBC 代码有没有漏关游标可以走 TaoToken 的模型对话接口Base URL 用https://taotoken.net/apiKey 在控制台生成。长期做这类代码审查和 Agent 辅助排查的话Coding Plan 会更划算接入文档在官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content上能找到API Keys 页面在控制台里。把工具用顺手排查这类问题会快很多。