Oracle JDBC的fetchSize需配合TYPE_FORWARD_ONLY和CONCUR_READ_ONLY才生效,否则缓存全结果集;游标依附Connection,关闭连接即失效;stream()非懒加载,须先确保流式抓取;服务端open_cursors限制需监控。
Oracle JDBC的fetchSize设置为什么没生效
java里调用setfetchsize()后内存还是爆了,大概率是因为oracle驱动默认忽略这个值——它只在resultset.type_forward_only且resultset.concur_read_only下才真正启用流式抓取。其他类型(比如scroll_insensitive)会强制缓存整结果集。
- 必须显式创建Statement时指定类型:
conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY) - 如果用了Spring JdbcTemplate,得配
setFetchSize()并确保底层不包装成可滚动ResultSet - MyBatis用户注意:
fetchType="EAGER"或resultType为集合时,框架可能提前全量加载,要改用Cursor返回类型 - Oracle 12c+支持
OFFSET ... FETCH分页,但那是逻辑分页,不是流式,别混用
Oracle游标生命周期与Connection绑定关系
Oracle的游标(cursor)不是独立资源,它依附于物理连接(Connection)和事务上下文。一旦Connection.close()或事务提交/回滚,游标就自动关闭——哪怕ResultSet还在遍历中,下次next()就会抛SQLException: Closed Connection。
- 别把Connection塞进线程池或连接池的“自动回收”逻辑里,尤其在长耗时流处理中
- 用
try-with-resources时,确保ResultSet、Statement、Connection按正确顺序关闭(ResultSet → Statement → Connection) - Web应用里常见坑:Servlet容器超时中断请求,Connection被池回收,但后台线程还在读ResultSet → 直接报错
stream()方法在Oracle JDBC 4.2+中的真实行为
JDBC 4.2引入ResultSet.stream(),但它只是把当前已拉到JVM内存的行转成Stream,**不是懒加载**。如果驱动还没开始流式抓取(比如fetchSize没设对、类型不对),stream()一调用就触发全量加载,OOM照旧。
- 必须先确认
ResultSet处于真正的流模式:检查getFetchSize()返回值是否大于0,且isClosed()为false - Oracle官方文档明确写:“stream() does not change fetch behavior”,它只是语法糖
- 替代方案更稳:手动while(
rs.next()) + 处理单行,避免任何中间集合 - 若真要用Stream,务必配合
rs.setFetchSize(100)+ 正确Statement类型,再调rs.stream().forEach(...)
Oracle服务端游标限制与v$open_cursor监控
即使Java端做对了,Oracle服务端也有游标上限(open_cursors参数,默认300)。每条未关闭的ResultSet对应一个服务端游标,大量并发流查询容易触顶,报错ORA-01000: maximum open cursors exceeded。
- 查当前使用:
SELECT sid, sql_id, COUNT(*) FROM v$open_cursor GROUP BY sid, sql_id ORDER BY COUNT(*) DESC - Java里别复用Statement执行不同SQL——每个Statement绑一个游标,用完立刻close
- 批量处理时,避免“一个Connection开N个Statement”,改用单Statement循环executeQuery()
- DBA可临时调大
open_cursors,但治标不治本;根本解法是缩短ResultSet生命周期
最常被忽略的一点:Oracle的流式读取依赖网络包大小(oracle.net.tns_address里的SDU)和JDBC驱动版本。11g驱动对fetchSize支持有bug,12c+驱动才稳定。别迷信配置项,先验驱动版本和实际抓取行为。
立即学习“Java免费学习笔记(深入)”;










