ora-01000 是 preparedstatement/statement 未关闭导致的游标泄漏,非数据库配置问题;每个会话游标数受限于 open_cursors 参数,连接池复用下泄漏会持续累积,需通过 v$open_cursor 定位泄漏会话,并确保正确 close() 或使用 try-with-resources。

ORA-01000 不是数据库配小了,而是 Java 应用在 Oracle 会话里开了太多 PreparedStatement 或 Statement,却没关。
Oracle 每个会话的游标资源是独立且有限的,默认 open_cursors 常为 300 或 500。只要一个连接被复用(比如 HikariCP、Druid 这类连接池),而代码里反复 conn.prepareStatement() 却不 close(),游标就只增不减——哪怕 conn.close() 了,物理连接归还池子,游标仍挂在会话上。
它最典型的症状是:服务刚启正常,跑几小时后开始报错,重启又恢复。这不是偶发故障,是泄漏在缓慢累积。
v$open_cursor 查谁在堆积游标
你得先确认是不是真有泄漏,而不是凭感觉改参数。
-
连 DBA 账号执行:
SELECT s.sid, s.serial#, s.username, s.machine, s.program, COUNT(*) AS cursor_count FROM v$open_cursor oc JOIN v$session s ON s.sid = oc.sid GROUP BY s.sid, s.serial#, s.username, s.machine, s.program ORDER BY cursor_count DESC;
关注
program列(比如jdbc:oracle:thin:@...)和对应cursor_count。如果某个会话长期维持在 200+,基本就是你的应用在漏。-
再查上限:
SELECT name, value FROM v$parameter WHERE name = 'open_cursors';
别跳过这步——有些环境 open_cursors 被设成 100,那根本撑不住任何批量逻辑。
PreparedStatement 在循环里反复 new 是高危写法
这是线上最常见泄漏源,尤其在批量 ID 生成、对账、导出等场景。
- 错误模式:
for (Order order : orders) { PreparedStatement ps = conn.prepareStatement("INSERT INTO t VALUES (?)"); ps.setLong(1, order.getId()); ps.executeUpdate(); // ❌ 没 close() }
每次 prepareStatement() 都在 Oracle 端开一个新游标,循环 1000 次 → 1000 个游标卡死在会话里。
-
正确做法分两种:
- 单条 SQL 复用:把
ps提到循环外,ps.close()放在循环后; - 多条不同 SQL:必须每个
ps自己close(),或用try-with-resources包住。
- 单条 SQL 复用:把
-
更安全的写法(JDK 7+):
try (PreparedStatement ps = conn.prepareStatement(sql)) { for (Order order : orders) { ps.setLong(1, order.getId()); ps.addBatch(); } ps.executeBatch(); }注意:addBatch()+executeBatch()只开 1 个游标,不是 N 个。
连接池的 statementCacheSize 是隐藏雷区
很多连接池(Druid 默认 100、WebLogic 默认 200、UCP 默认开启)自带语句缓存。它本意是提升性能,但副作用是:调用 ps.close() 后,语句不是释放游标,而是放回缓存——下次 prepareStatement() 可能复用,也可能新建,游标数照样涨。
- 快速验证是否是它惹的祸:临时禁用缓存。
- JDBC URL 加参数:
;statementCacheSize=0 - Spring Boot 配置:
spring: datasource: hikari: data-source-properties: statementCacheSize: 0 - Druid 用户设
statementCacheSize: 0(注意不是maxOpenPreparedStatements)
- JDBC URL 加参数:
压测对比游标增长趋势,如果禁用后稳定在个位数,那八成就是缓存机制在“假关闭、真堆积”。
游标泄漏最难缠的地方在于:它不报错在当场,而是在会话复用几十次之后才爆发;它不靠堆栈定位,得靠 v$open_cursor 反查;它甚至不看你有没有 conn.close(),只认 ps.close() 和 rs.close() 是否真正执行。
别信“连接池会帮我管好一切”,Oracle 的游标生命周期,只听 JDBC 规范,不听连接池宣传。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











