ORA-01000 根源是 Statement 未关闭导致 Oracle 服务端游标泄漏,ResultSet 关闭无效;必须显式或通过 try-with-resources 按 ResultSet→Statement→Connection 顺序关闭,连接池场景下尤其易发。
ORA-01000 错误本质是数据库端游标泄漏
不是 java 代码“没关连接”那么简单,而是 oracle 数据库对每个 statement(含 preparedstatement、callablestatement)分配一个服务器端游标,resultset 本身不占游标,但依赖其关联的 statement。当应用反复创建 statement 却未显式关闭,且连接被复用(如连接池场景),游标数就会持续累积,直到触发 ora-01000: maximum open cursors exceeded。
关键点:Oracle 的 open_cursors 参数默认常为 300 或 500,远低于连接池常见连接数 × 每连接并发语句数。
- 连接池中一个
Connection被复用 10 次,每次执行 2 条 SQL 且未关Statement→ 累积 20 个游标 -
ResultSet关闭不解决游标问题,必须关它背后的Statement - 即使用了
try-with-resources,若Statement是在方法外创建、传入再执行,资源管理容易脱节
Java 中 Statement 和 ResultSet 必须成对关闭,顺序不能错
关闭顺序有依赖关系:ResultSet → Statement → Connection。虽然 JDBC 规范允许只关 Statement(它会自动关关联的 ResultSet),但显式关闭更可控,尤其在异常路径下。
错误写法示例(漏关或顺序反):
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM t");
// 忘了 rs.close() 和 stmt.close()
// 或者先 conn.close() —— 此时 stmt 和 rs 可能仍被持有,游标未释放推荐做法(JDK 7+):
立即学习“Java免费学习笔记(深入)”;
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM t")) {
while (rs.next()) {
// 处理数据
}
} // 自动按 rs → stmt → conn 顺序关闭- 不要在
finally块里单独关Connection,除非你确定Statement和ResultSet已关且无异常干扰 - 若需复用
Statement执行多条 SQL,每次executeQuery()/executeUpdate()后,旧ResultSet会被自动关闭,但你仍需在最终不再使用该Statement时调用stmt.close() - 使用
PreparedStatement时同理,ps.close()才真正释放游标
连接池环境下最容易忽略的三个泄漏点
连接池让 Connection.close() 变成“归还连接”,而非真正断开,这掩盖了 Statement 和 ResultSet 未关的问题——游标还在 Oracle 端挂着。
-
在 DAO 方法里 new Statement 但没关:比如
public List<x> findX(Connection conn)</x>中直接conn.createStatement(),调用方不负责关,也没用 try-with-resources -
流式处理 ResultSet 时提前 return/throw:循环中遇到条件直接
return,rs和stmt没机会关 -
日志或监控代理拦截了 close 调用:某些 AOP 日志框架(如旧版 P6Spy)可能包装了
Statement,但未正确委托close(),导致实际未释放
验证方式:查 Oracle 当前会话游标占用:SELECT sql_id, child_number, open_cursor_count FROM v$open_cursor WHERE sid = ?,配合应用线程 ID 定位泄漏源头。
PreparedStatement 缓存与游标的关系要分清
很多人以为开了 PreparedStatement 缓存(如 HikariCP 的 cachePrepStmts=true)就能避免游标增长,其实不然。缓存的是客户端 PreparedStatement 对象,服务端游标仍由每个执行实例独占。同一 SQL 缓存 10 个 PreparedStatement,并发执行 10 次,仍是 10 个打开游标。
- 缓存减少的是客户端对象创建开销,不减少 Oracle 游标数
- 如果 SQL 里用了大量动态拼接(如
"WHERE id IN (" + ids.join(",") + ")"),会导致每种参数组合都生成新PreparedStatement,加剧游标膨胀 - 真正省游标的方式是:复用同一个
PreparedStatement实例多次execute,并在不用时及时close()
游标泄漏往往藏在“看起来没问题”的循环、异步回调或异常分支里,检查时别只盯 Connection,重点扫 Statement 创建点是否都有对应 close() 或资源块包裹。










