Oracle的setFetchSize()常无效,因驱动默认行缓冲且服务端忽略请求,须显式启用oracle.jdbc.defaultRowPrefetch或在executeQuery()前设置;仅对ResultSet生效,受驱动版本、数据类型(如LONG)、网络与内存影响,需合理配置并验证。
为什么 setFetchSize() 在 Oracle JDBC 里经常没效果
oracle 的 setfetchsize() 默认不生效,不是你代码写错了,而是驱动默认用「行缓冲」而非「列缓冲」,且服务端会忽略客户端的 fetch size 请求——除非显式启用 oracle.jdbc.defaultrowprefetch 或在 statement 上主动设置。更关键的是:它只对 resultset 生效,对 preparedstatement 的批量插入/更新无效。
常见错误现象:setFetchSize(1000) 后用 Wireshark 抓包,发现每次仍只传几十行;或者日志里看到 ORA-01000: maximum open cursors exceeded,其实是 fetch size 太小导致频繁往返、游标堆积。
- 必须在
executeQuery()前调用setFetchSize(),之后调用无效 - 连接 URL 加
?oracle.jdbc.defaultRowPrefetch=1000可全局生效,但无法动态调整 - Oracle 12c+ 支持服务端 hint
/*+ FETCH_SIZE(1000) */,但需配合驱动参数oracle.jdbc.useFetchSizeWithLongColumn=true - 如果查询含
LONG、XMLTYPE或大BLOB,fetch size 会被自动降级为 1,得加useFetchSizeWithLongColumn=true强制启用
PreparedStatement 的 setFetchSize() 怎么设才真正起作用
很多人在 PreparedStatement 上调 setFetchSize() 却发现没变化,是因为 Oracle 驱动对预编译语句的 fetch size 处理更保守:它只在执行 executeQuery() 时读取当前值,且受连接属性 defaultRowPrefetch 覆盖。换句话说,代码里设了,但驱动可能直接无视。
使用场景:分页导出百万级订单数据,不想 OOM,也不愿用 ROWNUM 分页(性能差)。
- 优先在获取
PreparedStatement后、executeQuery()前立即调用setFetchSize(500) - 避免复用同一
PreparedStatement多次查询不同 SQL —— fetch size 设置会残留,影响下一次 - 若用 HikariCP 等连接池,确保连接池未重置
Statement属性(Hikari 默认不会,但某些老版本 Druid 会) - 验证是否生效:开启 JDBC 日志(
oracle.jdbc.Trace=true),搜索fetchSize和rowsFetched
Fetch Size 设太大反而变慢?看网络和内存怎么博弈
设成 10000 不一定比 100 快。Oracle 客户端一次从服务端拉取的数据,会先缓存在 JVM 堆内,再逐行交给应用。fetch size 过大,单次网络包超 MTU,触发 TCP 分片;JVM 一次性分配大数组,GC 压力陡增;服务端 PGA 内存占用也会上升,可能被 DBA 杀掉。
性能影响关键点:网络延迟高(比如跨机房查库)、结果集字段多(尤其含 VARCHAR2(4000))、JVM 堆小于 2G。
- 建议起始值用
100 ~ 500,再按实际 RT 和 GC 日志调整 - 用
jstat -gc <pid>观察YGCT是否突增;用netstat -s | grep -i "retransmit"看重传率 - Oracle 服务端参数
SQLNET.SEND_TIMEOUT和RECV_TIMEOUT要大于 fetch 大块数据的预期耗时,否则直接断连 - 别迷信“越大越好”——实测过 fetch size=5000 时吞吐反降 30%,因服务端 PGA 分配失败退回到单行模式
Spring JdbcTemplate 怎么安全透传 Fetch Size
Spring 的 JdbcTemplate 默认把 setFetchSize() 当作 hint,不保证传递。它内部用 Connection.prepareStatement() 创建语句,但没暴露接口让你干预 Statement 实例。直接在回调里设,往往太晚。
正确做法不是改 Spring 源码,而是绕过封装,自己管 Statement 生命周期。
- 用
JdbcTemplate.getDataSource().getConnection()拿原生连接,手动创建PreparedStatement,再设setFetchSize() - 或实现
PreparedStatementSetter,并在setValues()里强转PreparedStatement为OraclePreparedStatement(需引入ojdbc8依赖) - 千万别用
@Transactional包裹手动连接操作——事务上下文会丢失 - 如果必须用
query()方法,可传入自定义ResultSetExtractor,并在其extractData()内通过rs.getStatement().getFetchSize()反查是否生效
最常被忽略的一点:Oracle 驱动版本低于 19.3 时,即使代码全对,setFetchSize() 在某些 JDK 11+ 场景下也会静默失效——得升级 ojdbc 或加 JVM 参数 -Doracle.jdbc.useFetchSizeWithLongColumn=true 强制唤醒。











