核心是启用mysql服务端游标:jdbc url加usecursorfetch=true,preparedstatement设type_forward_only+concur_read_only并调用setfetchsize(integer.min_value),配合流式写入与及时资源释放,避免oom。

Java 中用 JDBC 处理数据库游标进行大数据查询,核心目标是避免一次性加载全量结果导致内存溢出(OOM)或连接耗尽。关键不在于“创建游标”,而在于**激活服务端游标机制 + 控制客户端消费节奏 + 严格释放资源**。MySQL 默认不启用游标,必须显式配置驱动参数和 fetch 策略,否则即使写了 setFetchSize 也无效。
启用 MySQL 服务端游标
MySQL 驱动默认将整个结果集缓存在客户端内存中。要真正启用服务端游标,需同时满足两个条件:
-
JDBC URL 加参数:
?useCursorFetch=true&zeroDateTimeBehavior=convertToNull(useCursorFetch=true是 MySQL 特有开关,必须开启) -
Statement 设置游标类型与 fetchSize:
PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);ps.setFetchSize(Integer.MIN_VALUE); // 这是 JDBC 标准流式标志,不是设成 100 或 1000
⚠️ 注意:仅设 setFetchSize(100) 在 MySQL 下通常无效;Integer.MIN_VALUE 才会触发驱动走游标协议,让数据真正“按需拉取”。
流式读取 + 及时释放 LOB 资源
遇到大字段(如 TEXT、CLOB、BLOB)时,不能调用 rs.getString("col") 或 rs.getBlob("col").getBytes()——这会把整段内容载入内存。必须立即用流式接口处理:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 对
CLOB:用clob.getCharacterStream()获取Reader,边读边处理,且确保try-with-resources自动关闭 - 对
BLOB:用blob.getBinaryStream()获取InputStream - 绝对避免
clob.length()、clob.getSubString()、blob.length()等触发全量加载的操作
Oracle 用户额外注意:URL 中加 ?implicitCachingEnabled=false,防止驱动内部缓存 LOB 句柄。
连接与事务生命周期管理
游标依赖底层数据库连接持续有效。一旦连接关闭或事务提交/回滚,游标即失效,后续 rs.next() 会抛异常。
- 流式查询期间,连接不能提前关闭,也不能被连接池回收(需配置足够长的
maxWait和minIdle) - 不建议在长事务中做百万行流式处理——容易触发事务超时或锁表。推荐方案:
✅ 开启非事务连接(autoCommit=true)
✅ 或手动控制事务边界:只在必要时开启事务,处理完一批再提交 - 若处理过程含写操作(如边读边更新),建议用“读连接 + 写连接”分离架构,避免读游标阻塞写入
MyBatis 场景下的等效配置
如果项目用 MyBatis,无需手写 JDBC,但原理一致:
- 全局开启:
mybatis.configuration.settings.useCursorFetch=true - Mapper 方法返回
Cursor<user></user>,并在 service 层用while(cursor.hasNext()) { cursor.next(); }遍历 - 或使用
ResultHandler回调,适合导出类场景:sqlSession.select("selectLargeData", params, new CustomResultHandler()); - 务必配合
try-with-resources或显式sqlSession.close(),否则游标和连接均泄漏
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










