全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-20 6 分钟 ✍️ juanwangdev

结果集大小控制

查询大量数据时,如果不加控制地将结果全部加载到内存,极易导致 OOM。本文介绍三种核心控制手段。

fetchSize 控制

fetchSize 告知 JDBC 驱动每次从数据库拉取多少行数据,是控制内存消耗的第一道防线。

全局配置

XML
<settings>
    <setting name="defaultFetchSize" value="100"/>
</settings>

语句级别配置

XML
<select id="findLargeResult" resultType="User" fetchSize="200">
    SELECT * FROM user WHERE status = 1
</select>

fetchSize 工作原理

XML
数据库 ──→ JDBC驱动缓冲 ──→ 应用内存
    每次 fetchSize 行       逐批读取
fetchSize 值行为
0 或未设置驱动默认行为(通常是全部加载)
正整数 N每次从数据库拉取 N 行
-1使用驱动默认值

注意:fetchSize 只是给数据库的提示,不同数据库驱动的实现不同,实际效果需验证。

各数据库驱动对 fetchSize 的支持

数据库fetchSize > 0 效果
MySQL需配合 useCursorFetch=true 使用
PostgreSQL默认支持,生效良好
Oracle默认支持,生效良好

MySQL 需在 JDBC URL 中开启游标抓取:

Java
jdbc:mysql://localhost:3306/db?useCursorFetch=true&defaultFetchSize=100

流式查询

流式查询逐行处理结果集,内存中只保留当前行数据,适合超大数据量场景。

MyBatis Cursor 方式

XML
<select id="findAllUsers" resultType="User">
    SELECT * FROM user
</select>
Java
// Mapper 接口返回 Cursor
Cursor<User> findAllUsers();

// 使用 Cursor 流式遍历
try (Cursor<User> cursor = mapper.findAllUsers()) {
    for (User user : cursor) {
        processUser(user);
    }
}

注意:Cursor 必须在 SqlSession 未关闭前遍历,且需要保持数据库连接打开。

MyBatis ResultHandler 方式

XML
<select id="processAllUsers" resultType="User">
    SELECT * FROM user
</select>
Java
// 使用 ResultHandler 逐行处理
sqlSession.select("com.example.mapper.UserMapper.processAllUsers",
    new ResultHandler<User>() {
        @Override
        public void handleResult(ResultContext<? extends User> context) {
            User user = context.getResultObject();
            processUser(user);
        }
    });

ResultHandler 会在每获取一行数据时回调 handleResult 方法。

Cursor 与 ResultHandler 对比

特性CursorResultHandler
遍历方式Iterator 遍历回调函数处理
内存占用仅当前行仅当前行
使用方式更直观需要实现接口
适用场景需要控制遍历流程纯逐行处理
返回值Cursorvoid

内存管理最佳实践

1. 分页查询

对于需要展示给用户的列表,必须分页:

XML
<select id="findUsersByPage" resultType="User">
    SELECT id, username, email FROM user
    ORDER BY id
    LIMIT #{offset}, #{pageSize}
</select>
Java
// 合理分页
int pageSize = 20;
int pageNum = 1;
int offset = (pageNum - 1) * pageSize;
List<User> users = mapper.findUsersByPage(offset, pageSize);

2. 只查询必要的列

text
<!-- 反例:加载所有列 -->
<select id="exportEmails" resultType="User">
    SELECT * FROM user
</select>

<!-- 正例:只查需要的列 -->
<select id="exportEmails" resultType="String">
    SELECT email FROM user WHERE status = 1
</select>

3. 分批处理超大结果集

text
public void processAllUsersInBatches() {
    int batchSize = 1000;
    int offset = 0;
    while (true) {
        List<User> batch = mapper.findUsersByPage(offset, batchSize);
        if (batch.isEmpty()) {
            break;
        }
        processBatch(batch);
        offset += batchSize;
    }
}

要点总结

  • fetchSize 控制每次从数据库拉取的行数,减少内存峰值
  • MySQL 需开启 useCursorFetch=true 才能使 fetchSize 生效
  • Cursor 和 ResultHandler 实现流式查询,内存中仅保留当前行
  • 面向用户的列表必须分页,避免一次性加载全部数据
  • 只查询必要的列,减少每行内存占用
  • 超大数据量使用分批查询,避免长时间持有数据库连接

📝 发现内容有误?点击此处直接编辑

← 上一篇 批量操作事务控制
下一篇 → 复杂查询封装
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库