ResultSet.next()卡住因fetchSize未生效:须在executeQuery()前设setFetchSize(),配合setAutoCommit(false)和FETCH_FORWARD;游标未关致ORA-01000;LOB/DATE字段用流式API防OOM;分页需基于主键游标而非ROWNUM。

为什么ResultSet.next()卡住不动,其实是fetchSize没生效
Oracle JDBC默认的fetchSize是10,意味着每次next()实际只从服务端拉10行——对千万级表,光网络往返就上百万次。但光设setFetchSize(1000)没用:Oracle驱动要求必须在executeQuery()前设置,且需配合setAutoCommit(false)和setFetchDirection(ResultSet.FETCH_FORWARD)才真正启用流式拉取。
常见错误是先executeQuery()再调setFetchSize(),此时JDBC会静默忽略;或在连接串里加defaultRowPrefetch=1000,但该参数仅影响Statement创建时的默认值,不覆盖后续显式设置。
- 必须在
PreparedStatement创建后、executeQuery()前调用setFetchSize(500) - 连接需关闭自动提交:
connection.setAutoCommit(false) - 显式设置方向:
rs.setFetchDirection(ResultSet.FETCH_FORWARD) - 避免调用
rs.last()或rs.getRow()等触发全量加载的操作
Oracle游标未关闭导致ORA-01000:打开游标数超限
流式读取时若每批处理后不显式关闭ResultSet和PreparedStatement,Oracle服务端游标会一直持有。尤其在循环分页(如OFFSET/LIMIT)场景下,每个查询都开新游标,很快触达数据库open_cursors上限(默认300),报错ORA-01000: maximum open cursors exceeded。
正确做法是用try-with-resources确保资源释放,但注意:Oracle JDBC 12.1+驱动中,ResultSet.close()不会立即释放服务端游标,需等GC或显式调用statement.close()才会真正关闭。
立即学习“Java免费学习笔记(深入)”;
- 必须用
try (PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { ... } - 避免在循环内反复创建
PreparedStatement,复用同一实例并重设参数 - 确认数据库
open_cursors参数足够(建议≥1000):SELECT value FROM v$parameter WHERE name = 'open_cursors'
日期/大字段导致内存暴涨:别让getString()偷偷加载LOB
Oracle的CLOB、BLOB、DATE类型在JDBC中默认以String形式读取时,驱动会将其完整加载进JVM堆内存。一个10MB的CLOB字段被rs.getString("content")调用,立刻吃掉同等内存——千万行下来直接OOM。
解决方案不是不用getString,而是针对性绕过:对已知大字段,改用流式API;对DATE,用getTimestamp而非getString避免字符串解析开销。
- 读
CLOB用rs.getCharacterStream("col_name"),按需读取字符流 - 读
BLOB用rs.getBinaryStream("col_name") - 禁止对
DATE列调用getString(),统一用getTimestamp() - 检查表结构,提前识别大字段列,写死字段名白名单,不在循环里动态调用
getString
分批处理时ORDER BY失效:Oracle ROWNUM伪列的陷阱
想“流式分页”读取,很多人写SELECT * FROM (SELECT a.*, ROWNUM rn FROM table a) WHERE rn BETWEEN ? AND ?。这看似能分批,但Oracle在应用ROWNUM前不保证顺序——若原表无ORDER BY,每批数据会乱序甚至重复;加了ORDER BY又会导致全表排序后再截断,失去流式意义。
真流式分页唯一可靠方式是基于单调递增字段(如主键ID)做游标分页:WHERE id > ? ORDER BY id FETCH NEXT 1000 ROWS ONLY(Oracle 12c+),或传统WHERE id > ? AND ROWNUM (需确保<code>id有索引)。
- 绝对不要在子查询里用
ROWNUM+ORDER BY组合 - 优先用
FETCH FIRST语法(Oracle 12c+),它明确支持流式分页且走索引 - 若用
ROWNUM方案,必须在最外层WHERE过滤,且ORDER BY字段必须有索引 - 测试时用
EXPLAIN PLAN确认执行计划是否走了索引范围扫描(INDEX RANGE SCAN)
流式读取的核心不是“怎么快”,而是“怎么不崩”:控制单次网络载荷、严防游标泄漏、绕过大字段内存陷阱、用对分页机制。Oracle的流式能力很真实,但所有开关都藏在细节里——漏掉任意一环,千万行就会变成OOM、锁表或超时。


















