ORA-01000错误本质是应用端游标泄漏,非数据库参数不足;Spring Boot中PreparedStatement复用不当、循环拼接SQL、手动获取Connection未关闭、MyBatis ResultHandler未读完或PL/SQL中REF CURSOR未关闭,均会导致游标持续占用会话。
ora-01000 错误不是数据库配得不够大,而是 spring boot 应用端持续泄漏游标句柄——哪怕连接已归还池中,游标仍被绑定在 oracle 会话里不释放。
Spring Boot 中 PreparedStatement 复用不当导致游标堆积
Druid/Hikari 连接池默认开启 PreparedStatement 缓存(cachePrepStmts=true),但缓存 ≠ 复用。每次调用 jdbcTemplate.update() 或手动 conn.prepareStatement(sql),只要 SQL 字符串不同(比如含拼接 ID、动态表名),Oracle 就会解析为新语句、分配新游标。循环中生成 500 条不同 SQL,就可能打开 500 个未关闭游标。
- 避免在循环内拼接 SQL:❌
"UPDATE t SET v = " + i→ ✅ 改用参数化"UPDATE t SET v = ?" - 禁用 PreparedStatement 缓存验证是否为缓存引发:在 JDBC URL 后加
&cachePrepStmts=false&prepStmtCacheSize=0 - 若必须动态 SQL,优先用 MyBatis 的
批量操作,而非 Java 层循环建 Statement
JdbcTemplate 手动获取 Connection 时忘记 close() 或 closeStatement()
JdbcTemplate 自动管理连接和 Statement 的前提是全程走它的封装方法(如 query(), update())。一旦你调用 jdbcTemplate.getDataSource().getConnection() 拿到原始 Connection,后续所有 PreparedStatement 和 ResultSet 都必须显式关闭——JdbcTemplate 不再介入。
- 错误写法:
Connection conn = ds.getConnection(); PreparedStatement ps = conn.prepareStatement(...); ps.executeUpdate();→ps和conn都没关 - 正确写法:用
try-with-resources包裹PreparedStatement,且确保conn.close()被调用(连接池下 close() 实际是归还) - 更稳妥:直接用
JdbcTemplate的批量方法,如batchUpdate(String sql, BatchPreparedStatementSetter setter),它内部自动复用同一PreparedStatement
MyBatis 场景下 ResultHandler 或 Cursor 没读完就返回
MyBatis 的 selectCursor() 或流式查询(fetchSize > 0 + ResultHandler)底层仍依赖 OracleResultSet。如果 handler 逻辑提前 return 或抛异常,未消费完的 ResultSet 会导致游标卡住——GC 不保证及时回收,尤其高并发下。
- 必须确保
ResultHandler.handleRow()被完整执行,或捕获异常后主动调用cursor.close() - 避免在
ResultHandler中做耗时操作(如远程调用),否则 ResultSet 长时间挂起 - 检查 XML 中是否误设
fetchSize="1"却未配合流式处理,导致每行都开新游标
最隐蔽的泄漏点是 PL/SQL 块中隐式打开的 REF CURSOR:即使 Java 端只调一次 execute(),只要存储过程里有 OPEN cur FOR ... 且未在过程末尾 CLOSE cur,这个游标就永远留在会话里。这类问题只能靠 DBA 查 v$open_cursor 定位具体 SQL,再反推代码逻辑。











