jdbcpagingitemreader是oracle超大数据集的首选读取器,因其分页机制避免内存溢出;而jdbccursoritemreader易触发outofmemoryerror。

JdbcPagingItemReader 是 Oracle 超大数据集的首选读取器
直接用 JdbcCursorItemReader 读千万级 Oracle 表,大概率触发 java.lang.OutOfMemoryError: Java heap space。它会把整个结果集一次性加载进内存(即使底层 ResultSet 是游标),尤其在 Oracle 中默认不启用流式 fetch,风险更高。而 JdbcPagingItemReader 通过分页 SQL + 显式排序键控制数据边界,每次只拉取一个 page 的数据(如 1000 条),内存占用稳定可控。
关键前提是:必须指定唯一、单调、可索引的排序字段(如主键 ID 或带索引的时间戳 CREATE_TIME)。Oracle 不支持 OFFSET/LIMIT,所以依赖 OraclePagingQueryProvider 生成 ROW_NUMBER() 形式的分页语句。
-
JdbcPagingItemReader需要setSortKeys—— 缺失会导致IllegalArgumentException: Sort keys must be specified - Oracle 分页依赖
ROW_NUMBER() OVER (ORDER BY ...),所以排序字段必须有索引,否则分页查询极慢 -
fetchSize建议设为与 chunk-size 相同(如都设为 1000),避免 JDBC 驱动额外缓冲
配置 OraclePagingQueryProvider 时绕开常见 SQL 陷阱
Oracle 对分页 SQL 语法敏感,OraclePagingQueryProvider 自动生成的语句若没对齐字段别名或嵌套层级,容易报 ORA-00904: "ID" invalid identifier 或 ORA-00979: not a GROUP BY expression。核心是确保 select 子句中所有字段都显式出现在 order by 中,且别名一致。
示例安全写法:
OraclePagingQueryProvider queryProvider = new OraclePagingQueryProvider();
queryProvider.setSelectClause("SELECT ID, NAME, EMAIL, STATUS");
queryProvider.setFromClause("FROM CUSTOMER");
queryProvider.setWhereClause("WHERE STATUS = :status");
Map<string order> sortKeys = new LinkedHashMap();
sortKeys.put("ID", Order.ASCENDING); // 必须与 select 中字段名完全一致
queryProvider.setSortKeys(sortKeys);
</string>
- 避免在
select中使用*或函数表达式(如UPPER(NAME))作为排序字段,否则 provider 无法正确映射 -
where条件中的参数要用命名参数(:status),并配合setParameterValues(Map.of("status", "ACTIVE")) - 如果表有复合主键,选其中一个高基数、高选择性的字段做排序键,不要用
(col1, col2)元组形式
chunk-size 与 fetchSize 不匹配会拖慢吞吐量
chunk-size 控制 Spring Batch 每次提交事务的数据条数;fetchSize 控制 JDBC 每次从 Oracle 网络包里取多少行。两者不一致时,会出现“拉得多、写得少”或“写多次、拉一次”的错配。
比如 chunk-size=100,fetchSize=1000:每轮 writer 只处理 100 条,但 JDBC 已经预取了 1000 条到本地缓存,浪费内存且可能阻塞后续分页查询;反之 chunk-size=1000,fetchSize=100:JDBC 频繁往返数据库,网络开销陡增。
- 生产环境建议统一设为 500–2000(根据单条记录大小和 GC 压力调整)
- Oracle 官方驱动(ojdbc8)对 fetchSize > 1000 的支持更稳,低于 100 可能触发隐式批量 fetch
- 开启
setSaveState(true)(默认值),确保 job 失败重启时能从上一页最后 ID 继续,而不是重头查
大字段(CLOB/BLOB)导致分页性能断崖下跌
Oracle 分页查询若包含未过滤的 CLOB 字段,ROW_NUMBER() 会强制 materialize 整个 LOB 内容,即使你只读前几页。典型表现是第一页耗时几秒,第二页飙升至分钟级。
解决办法只有两个:要么在 select 中排除 CLOB 字段,后续用主键异步补查;要么加 where 条件提前过滤掉含大字段的记录。
- 错误示范:
SELECT ID, NAME, CONTENT_CLOB FROM DOC_TABLE—— 即使只取前 100 行,Oracle 仍可能扫描全部 LOB 段 - 正确做法:先用轻量查询获取 ID 列表,再用
IN批量查详情(需注意 IN 参数上限) - 如果必须带 CLOB,确保该字段上有函数索引(如
CREATE INDEX idx_doc_status ON DOC_TABLE (STATUS, DBMS_LOB.GETLENGTH(CONTENT_CLOB))),并用 where 限定长度
EXPLAIN PLAN 看一眼生成的分页 SQL 是否走了索引。











