核心问题是JDBC默认将全部ResultSet一次性加载进JVM堆内存;解决关键是开启流式查询:MySQL需连接参数useCursorFetch=true、创建Statement时指定TYPE_FORWARD_ONLY/CONCUR_READ_ONLY、调用setFetchSize(Integer.MIN_VALUE),并严格管控资源生命周期与数据传输精简。

Java 中 JDBC 查询大数据量时内存占用过高,核心问题不是“数据太大”,而是默认把整张结果集一次性加载进 JVM 堆里。解决方向很明确:不让数据全进内存,而是边取边处理、用完即丢。
必须开启流式查询(关键一步)
这是最直接有效的手段。JDBC 默认会缓存全部 ResultSet,需主动切换为流式模式:
- MySQL 连接串加参数:useCursorFetch=true&fetchSize=100(或 useServerPrepStmts=false 避免预编译干扰游标)
- 创建 Statement 时指定类型:createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)
- 执行前设置:stmt.setFetchSize(Integer.MIN_VALUE)(注意:设成正数如 100 是分批拉取,设成 MIN_VALUE 才是真正流式)
- PostgreSQL 则只需 setFetchSize(100) 并确保类型为 FORWARD_ONLY
严格控制资源生命周期
流式查询依赖连接长期存活,但资源不释放会导致句柄泄漏或连接池耗尽:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- ResultSet 和 Statement 必须在处理完全部数据后才 close;中途不能提前关闭,否则游标失效
- 推荐用 try-with-resources 包裹 Connection,但 ResultSet 要在外层循环中显式 close(或确保所有行已遍历完毕)
- 避免在流式查询过程中复用同一 Connection 执行其他 SQL,会触发 “Streaming result set is still active” 异常
精简数据传输与对象构建
减少每行数据在 JVM 中的内存 footprint:
立即学习“Java免费学习笔记(深入)”;
- 查什么取什么:禁用 SELECT *,只选业务需要的字段,尤其避开 BLOB、TEXT、JSON 等大字段
- 用基本类型或 String 接收,避免映射成完整实体类(如 new Order());可考虑用数组或 Map 临时承载一行
- 若用 MyBatis,改用 ResultHandler 或 Cursor<T>,不要返回 List<Entity>
配合数据库端调优
光靠 JDBC 端调整不够,服务端也要协同:
- MySQL 设置 max_allowed_packet 足够大(如 64M),避免因单包超限中断流式传输
- 确保查询字段有合适索引,尤其是排序、分页、WHERE 条件字段;流式查询仍要走索引扫描,否则变成慢速全表游标
- 对超大表,考虑按时间/ID 范围分段查询(如 WHERE id BETWEEN ? AND ?),比纯流式更可控、可并行

















