处理大结果集(Large Result Set)的核心思路是:避免一次性把全部数据放进内存,而是边取边处理、分批、流式、异步、可中断。下面按常见场景给你一套实用方案。
大结果集通常带来 3 个问题:
所以处理原则:
不一次性加载、不长时间占用连接、不阻塞主线程
最常见、最安全
SELECT * FROM orders
ORDER BY id
LIMIT 1000 OFFSET 0;
问题:
OFFSET 很大时性能差✅ 推荐:游标分页(Keyset Pagination)
SELECT * FROM orders
WHERE id > :last_id
ORDER BY id
LIMIT 1000;
适合:
避免:
SELECT *
改成:
SELECT id, status, amount FROM orders
-- 服务端游标
DECLARE cur CURSOR FOR SELECT ...
PreparedStatement ps = conn.prepareStatement(
sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
ps.setFetchSize(Integer.MIN_VALUE); // 流式
DECLARE cur CURSOR FOR SELECT ...
FETCH 1000 FROM cur;
✅ 适合:超大结果集、导出、迁移
不要 List<Entity> 全部接住
❌ 错误示例:
List<Order> list = queryAll(); // 危险
✅ 正确示例(Java Stream / Iterator):
try (Stream<Order> stream = repository.streamAll()) {
stream.forEach(this::process);
}
最常用、最稳
lastId = 0
while true:
rows = query(limit 1000, where id > lastId)
if rows empty:
break
process(rows)
lastId = max id
适合:
避免:
查全量 → 放内存 → 写文件
正确:
查一批 → 写一批 → flush
示例(CSV):
while (hasNext) {
List<Row> rows = fetchNext(1000);
writeToCsv(rows);
}
✅ 不占内存
✅ 可断点续传
GET /orders?page=1&size=100
❌ 禁止:
GET /orders/all
GET /orders?after=12345&limit=100
返回:
{
"data": [...],
"next": "12399"
}
✅ 对前端和数据库都友好
流程:
创建任务 → 返回 taskId
后台 worker 分批处理
生成文件 → 上传 OSS
✅ 用户体验好
✅ 可失败重试
适合 超大规模
worker1: id 1–1000000
worker2: id 1000001–2000000
| 错误 | 后果 |
|---|---|
| 一次性查全表 | OOM |
| 超大 OFFSET | 慢 + 锁 |
| 长事务 | 锁表、回滚段膨胀 |
| 流式但不关流 | 连接泄漏 |
| 不限制最大结果 | 被刷爆 |
| 场景 | 推荐方案 |
|---|---|
| API 查询 | 游标分页 |
| 数据同步 | 分批 + 游标 |
| 数据导出 | 流式 + 文件 |
| 定时任务 | 分批处理 |
| 报表 / 统计 | 预聚合 / 离线计算 |
如果你愿意,可以告诉我:
我可以直接给你可落地代码方案。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。