close(connection) 不释放 oracle 游标是因为连接池归还物理连接而非销毁会话,游标由 preparedstatement/resultset 创建并占用,必须显式关闭或用 try-with-resources 包裹三层资源逆序释放,且需禁用语句缓存、避免复用 preparedstatement 执行不同 sql、改单条为批量操作,最后才考虑调大 open_cursors。

为什么 close(Connection) 不释放 Oracle 游标
连接池(如 HikariCP、Druid)的 close() 只是把物理连接归还给池,不是销毁数据库会话。Oracle 每个会话维护独立的游标资源,PreparedStatement 或 Statement 创建时就在服务端分配了游标句柄,不显式关闭就一直占用。即使你反复 conn.close(),只要 ps.close() 没执行,游标就堆积在 v$open_cursor 里。
必须用 try-with-resources 包三层资源
只包 Connection 是无效的,必须让 Connection、PreparedStatement、ResultSet 各自独立出现在 try 括号中,才能确保按 ResultSet → PreparedStatement → Connection 逆序自动关闭:
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT * FROM t WHERE id = ?");
ResultSet rs = ps.executeQuery()) {
while (rs.next()) { /* 处理 */ }
} // 三者在此处依次关闭,游标真正释放
- 不能只包外层
Connection,否则ps和rs可能因异常跳过关闭 - 不能复用同一
PreparedStatement实例执行不同 SQL,参数类型或数量变化会触发隐式游标残留 - 批量操作别写 for 循环 + 单条
executeUpdate(),改用addBatch()+executeBatch()
禁用语句缓存是快速验证泄漏的关键
很多连接池和 Oracle JDBC 驱动默认开启语句缓存(例如 WebLogic 默认 statementCacheSize=200),这时调用 ps.close() 实际只是放回缓存,不是释放服务端游标。临时禁用后若 ORA-01000 消失,就确认是它惹的祸:
- JDBC URL 中加
;statementCacheSize=0(Thin 驱动) - HikariCP 用户在
application.yml中配:spring: datasource: hikari: data-source-properties: statementCacheSize: 0 - UCP 用户调用
setStatementCacheSize(0)
生产环境合理值通常是 20~50,别保留默认 200。
调大 open_cursors 是最后手段,且需 DBA 配合
open_cursors 是会话级软限制,默认常为 300。盲目设成 5000 不解决泄漏,反而掩盖问题:
- 先查当前值:
show parameter open_cursors - 热生效修改需 DBA 执行:
ALTER SYSTEM SET open_cursors = 1000 SCOPE=BOTH - 该参数不增加额外开销,但仅适用于确认单会话确实需大量并发游标(如动态拼 N 个报表查询)的场景
- 如果禁用缓存 + 修复代码后仍频繁超限,再考虑调参,并同步查
v$open_cursor确认是否真有大量未关闭游标
最易被忽略的是:CDB/PDB 架构下,每个 PDB 有自己的 open_cursors 设置,且 v$open_cursor 视图默认只显示当前会话,跨 PDB 排查需切换容器或用 CDB_* 视图。











