核心是启用JDBC流式查询:设ResultSet.TYPE_FORWARD_ONLY、CONCUR_READ_ONLY及setFetchSize(Integer.MIN_VALUE),MySQL需URL加useCursorFetch=true,避免SELECT*和TEXT/BLOB滥用,每行处理完立即释放资源。

Java 中 JDBC 处理大数据量结果集时内存溢出,核心原因是驱动默认将整个 ResultSet 一次性加载进 JVM 堆内存。哪怕只有几十万行、字段不多,对象封装+引用开销也极易突破 512MB~1GB。解决的关键不是“扛”,而是让数据“流起来”——按需读取、边取边处理、不囤积。
启用 JDBC 流式查询(MySQL 最常用)
这是最直接有效的方案,适用于 MySQL 且无需改 SQL 或业务逻辑:
- 连接 URL 必须添加参数:
useCursorFetch=true&defaultFetchSize=100 - 执行查询前,
Statement需显式设置游标模式:stmt.setFetchSize(Integer.MIN_VALUE);
(注意:仅当ResultSet.TYPE_FORWARD_ONLY+CONCUR_READ_ONLY时生效) - 此时 MySQL 服务端会开启服务器端游标(server-side cursor),JDBC 客户端只缓存当前 fetch batch 的数据(如 100 行),内存占用基本恒定在几百 KB 级别
避免 SELECT * 和冗余字段
字段数量直接影响单行内存开销。实测中,SELECT * 查 2 万行就 OOM,而只查 3 个 VARCHAR(50) 字段却能跑完 6 万行——差别就在对象膨胀和字符串常量池压力上:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 永远明确指定所需列,例如:
SELECT id, name, status FROM user WHERE ... - 禁用
SELECT COUNT(*)后再全表查的“两阶段”做法;如需总数,单独走聚合查询或用估算值 - 对
TEXT/BLOB类型字段尤其谨慎,必要时用getBinaryStream()或getClob()延迟加载
配合手动分批 + 关闭资源
流式查询虽好,但要求必须消费完全部结果或显式关闭 ResultSet,否则连接会被独占。实际编码中要严格遵循:
立即学习“Java免费学习笔记(深入)”;
- 用
try-with-resources包裹Connection、Statement、ResultSet - 循环中一旦完成某批次处理(比如每 1000 行做一次统计汇总),立即触发业务逻辑,不要累积到 List 中
- 若中途需中断(如异常/超时),务必在
finally或catch中调用rs.close()和stmt.close()
其他数据库适配要点
不同数据库启用流式/游标行为略有差异:
-
SQL Server:设置
statement.setFetchSize(100)即可触发游标模式,无需额外 URL 参数 -
Oracle:需配置
oracle.jdbc.fetchSize=100,并确保使用OraclePreparedStatement -
ClickHouse:JDBC 驱动默认支持流式,但需禁用
useServerPrepStmts=false,并设fetchSize为合理值(如 5000)

















