当前位置: 首页 > news >正文

深分页实战

sql

第一页查询
sql
SELECT *
FROM your_table_name
ORDER BY id DESC
LIMIT 100;
第二页及以后的查询
假设第一页最后一条记录的id是 prev_max_id(例如 9950)。

sql
SELECT *
FROM your_table_name
WHERE id < prev_max_id -- 核心:利用WHERE条件跳过之前的所有记录
ORDER BY id DESC
LIMIT 100;

一,结论

查询到数据,即时处理,不加入集合中,每次循环结束,都会释放这一次循环对象内存

二,代码

代码1

    public List<WarehouseSubBO> pageListForO2O(WarehouseSubQuery query, PageParam pageParam) {pageParam.setPage(1);pageParam.setSize(100);//主键ID降序,优先处理新创建的pageParam.setOrderBy("id DESC");WarehouseSubQuery pageQuery = new WarehouseSubQuery();pageQuery.setType(4);List<WarehouseSubBO> all = new ArrayList<>();while (true) {List<WarehouseSubBO> subBOPage = warehouseSubService.pageList(pageQuery, pageParam).getData();if (CollectionUtils.isEmpty(subBOPage)) {break;}all.addAll(subBOPage);pageQuery.setIdLt(subBOPage.get(subBOPage.size() - 1).getId());}return all;}

代码2

private List<WarehouseSubBO> findAllByCursorPagination(WarehouseSubQuery query, Integer pageSize) {List<WarehouseSubBO> allResults = new ArrayList<>();Long lastId = null;int pageCount = 0;final int maxPages = 1000;// 将PageParam提到循环外创建(重要优化)PageParam currentPage = new PageParam();currentPage.setPage(1);           // 固定值currentPage.setSize(pageSize);    // 固定值currentPage.setOrderBy("id DESC"); // 固定值while (pageCount < maxPages) {if (lastId != null) {query.setIdLt(lastId);}// 直接使用已经配置好的pageParam对象PageResult<WarehouseSubBO> pageResult = warehouseSubService.pageList(query, currentPage);List<WarehouseSubBO> currentPageData = pageResult.getData();if (CollectionUtils.isEmpty(currentPageData)) {break;}allResults.addAll(currentPageData);pageCount++;if (currentPageData.size() < pageSize) {break;}lastId = currentPageData.get(currentPageData.size() - 1).getId();}return allResults;
}

代码3

public void processStreaming() {while (true) {// 创建:currentPage列表 + 100个数据对象List<WarehouseSubBO> currentPage = getDataFromDB();if (currentPage.isEmpty()) break;// 处理数据(不创建其他引用)for (WarehouseSubBO item : currentPage) {saveToFile(item); // 处理完就不需要在内存中了}// 循环结束:currentPage和100个数据对象都可以被GC回收}// 方法结束时没有大量内存占用
}

代码4

/*** 流式处理大量数据(避免内存溢出)*/
public void processO2OWarehouses(WarehouseSubQuery query, Consumer<List<WarehouseSubBO>> processor) {PageParam pageParam = new PageParam();initPageParam(pageParam);WarehouseSubQuery pageQuery = createBaseQuery(query);Long lastId = null;int processedCount = 0;while (true) {if (lastId != null) {pageQuery.setIdLt(lastId);}List<WarehouseSubBO> currentPage = warehouseSubService.pageList(pageQuery, pageParam).getData();if (CollectionUtils.isEmpty(currentPage)) {break;}// 处理当前页数据,而不是保存到内存processor.accept(currentPage);processedCount += currentPage.size();lastId = currentPage.get(currentPage.size() - 1).getId();if (currentPage.size() < pageParam.getSize()) {break;}}log.info("流式处理完成,共处理{}条数据", processedCount);
}

三,疑问

1,内存占用问题

有几个占用内容的地方,被谁引用,什么时候释放。

比如

1>, List<WarehouseSubBO> currentPage ,从数据库查询出来?

2>,把所有数据存到list中 allResults.addAll(currentPage);

2,页码都是1,查询原理

3,if (lastId != null) 作用

if (lastId != null) {pageQuery.setIdLt(lastId);}

4,循环内对象释放内存时机

while (true) { // 1. 查询数据到内存 List<WarehouseSubBO> currentPage = getDataFromDB(); // ↑ 内存占用:currentPage对象 + 数据对象。

currentPage没有被集合引用的情况下。首先请解释是两个对象,在说明是循环每次完成释放,还是全部循环结束释放,还是方法结束释放

5,代码4中,推荐使用流式处理,怎么理解如下代码,怎么处理的

processor.accept(currentPage);

processor.accept(currentPage) 本身不释放内存,它只是传递数据。如何传递的,传给谁了,给出完整代码,上下文关系

6,不用5中流式处理,直接做保存处理,不行吗

可以

四,回答

4,循环内对象释放内存时机

结论:查询到数据,即时处理不加入集合中,每次循环结束,都会释放这一次循环对象内存

您问得非常到位!这里确实涉及到几个不同层次的对象和它们的生命周期。让我详细解释:

对象的层次结构

1. currentPage 变量本身(List对象)

  • 是什么:一个 ArrayList 或其它List实现的对象

  • 大小:很小(约几十字节),包含一些元数据(容量、大小等)

2. currentPage 中包含的数据对象(WarehouseSubBO对象)

  • 是什么:实际的业务数据对象

  • 大小:较大(几百字节到几KB每个),包含所有字段数据

java

// 内存中的实际结构:
currentPage (ArrayList对象, 很小)
│
├── elementData (Object[]数组, 中等)
│   │
│   ├── WarehouseSubBO对象1 (较大)
│   ├── WarehouseSubBO对象2 (较大)
│   ├── ...
│   └── WarehouseSubBO对象100 (较大)
│
├── size (int, 很小)
└── modCount (int, 很小)

内存释放时机

情况1:没有其他引用(流式处理理想情况)

java

while (true) {// 1. 创建currentPage列表对象和其中的数据对象List<WarehouseSubBO> currentPage = getDataFromDB();// 2. 处理数据(没有添加到其他集合)processData(currentPage);// 3. 循环结束,currentPage变量超出作用域
} // ← 这里currentPage和其中的数据都可以被GC回收

释放时机每次循环结束都可以释放

情况2:被其他集合引用(一次性加载)

java

List<WarehouseSubBO> allResults = new ArrayList<>();while (true) {List<WarehouseSubBO> currentPage = getDataFromDB();// currentPage中的数据被allResults引用!allResults.addAll(currentPage);// currentPage变量超出作用域,但数据还在allResults中
} // ← 只有currentPage列表容器可以被释放,数据对象无法释放!// allResults仍然持有所有数据对象的引用

释放时机方法结束后才可能释放(如果调用方不再引用)

详细的内存生命周期

图表

代码

具体的对象释放时机表

对象类型创建时机释放时机(无其他引用)释放时机(有其他引用)
currentPage列表对象每次循环开始每次循环结束每次循环结束
数据对象(WarehouseSubBO)每次查询数据库时每次循环结束方法结束后(或其他引用消失时)
allResults集合对象方法开始时方法结束时方法结束时

代码示例对比

示例1:每次循环都可释放(推荐)

java

public void processStreaming() {while (true) {// 创建:currentPage列表 + 100个数据对象List<WarehouseSubBO> currentPage = getDataFromDB();if (currentPage.isEmpty()) break;// 处理数据(不创建其他引用)for (WarehouseSubBO item : currentPage) {saveToFile(item); // 处理完就不需要在内存中了}// 循环结束:currentPage和100个数据对象都可以被GC回收}// 方法结束时没有大量内存占用
}

示例2:内存累积(不推荐)

java

public List<WarehouseSubBO> loadAll() {List<WarehouseSubBO> allResults = new ArrayList<>(); // 创建集合对象while (true) {// 创建:currentPage列表 + 100个数据对象List<WarehouseSubBO> currentPage = getDataFromDB();if (currentPage.isEmpty()) break;// 数据对象被allResults引用!allResults.addAll(currentPage);// 循环结束:只有currentPage列表对象可以被回收// 100个数据对象仍然被allResults引用,无法释放!}return allResults; // 所有数据对象仍在内存中
}

关键总结

  1. 两个不同对象

    • currentPage列表对象(容器,很小)

    • 数据对象(内容,很大)

  2. 释放时机

    • 无其他引用:每次循环结束都可释放

    • 有其他引用:只有容器可释放,数据要等到引用消失

  3. 流式处理优势:避免创建额外的引用,让GC可以及时回收内存

  4. 内存溢出风险:来自于数据对象被长期引用,而不是临时的小容器对象

这就是为什么流式处理可以处理海量数据而不会内存溢出的根本原因!

http://www.xdnf.cn/news/1366327.html

相关文章:

  • 计算机网络:HTTP、抓包、TCP和UDP报文及重要概念
  • GPT5的Test-time compute(测试时计算)是什么?
  • Legion Y7000P IRX9 DriveList
  • HTTP 与 HTTPS 深度解析:从原理到实际应用
  • 链表OJ习题(1)
  • 1. 并发产生背景 并发解决原理
  • pytest 并发执行用例(基于受限的测试资源)
  • 现代C++工具链实战:CMake + Conan + vcpkg依赖管理
  • week4-[一维数组]数码个数
  • k8s笔记02概述
  • C++|UDP通讯使用总结
  • HTML应用指南:利用GET请求获取MSN 天气数据并可视化
  • [系统架构设计师]应用数学(二十一)
  • list容器的使用
  • GNN:用MPNN(消息传递神经网络)落地最短路径问题模型训练全流程
  • 用 GSAP + ScrollTrigger 打造沉浸式视频滚动动画
  • 【Day 33】Linux-Mysql日志
  • DDR3入门系列(二)------DDR3硬件电路及Xilinx MIG IP核介绍
  • linux 正则表达式学习
  • 使用 gemini 来分析 github 项目
  • 安卓11 12系统修改定制化_____修改固件 默认给指定内置应用系统级权限
  • 大模型的思考方式
  • Java全栈开发实战:从Spring Boot到Vue3的项目实践
  • ZKmall开源商城多端兼容实践:鸿蒙、iOS、安卓全平台适配的技术路径
  • 8.25作业
  • [MH22D3开发笔记]2. SPI,QSPI速度究竟能跑多快,双屏系统的理想选择
  • Linux笔记9——shell编程基础-3
  • Tesseract OCR之页面布局分析
  • Linux系统的网络管理(一)
  • c# 读取xml文件内的数据