项目中大批量数据查询导致OOM

文章讨论了在处理大批量数据时可能导致的内存溢出问题,通过复现问题展示了查询整个表导致的Javaheapspace错误。为了解决这个问题,提出了两种解决方案:使用分页查询和流式查询。分页查询适用于数据展示,而流式查询适合数据处理。这两种方法能有效避免一次性加载所有数据到内存,从而防止内存溢出。

项目中有时候一次性将大批量数据都查出来到内存中导致内存占用过多很可能会导致内存溢出

问题复现

搭建一个本地项目。需求描述:查询表call_task中待拨打的数据进行拨打,call_task中一次可能会有大批量数据需要处理。本次准备了1万条数据。

/**
 * 查询数据执行拨打
 **/
@Override
public void waitingCall() {
    List<CallTask> waitingTaskList = list(Wrappers.<CallTask>lambdaQuery()
            .eq(CallTask::getCallStatus, "WAITING")
            .le(CallTask::getReqTime, new Date())
    );
    if (CollUtil.isEmpty(waitingTaskList)) {
        return;
    }

    call(waitingTaskList);
}

/**
 * 模拟执行具体拨打数据
 **/
private void call(List<CallTask> waitingTaskList) {
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

启动项目时将JVM堆内存的大小调整到合适数值(可以造成内存溢出的情况)。本次最大设置的20m,最小设置的10m

-Xmx20m -Xms10m

idea设置方式
选择 run/debug configurations 设置 VM options
在这里插入图片描述
想要更好的知道大概设置多少合适可以安装插件jProfier插件来进行分析,调用方法通过工具查看调用方法时内存是多少,根据该数据来进行设置
在这里插入图片描述
性能工具安装

  1. 在idea中下载jProfiler工具
  2. 下载jProfiler客户端工具 下载地址

启动程序执行方法,程序出现错误 java.lang.OutOfMemoryError: Java heap space
在这里插入图片描述

解决办法

问题复现之后,开始解决。
针对此问题查到的几种解决方式

使用分页查询,避免一次性查询所有的数据,分批次处理

public void methodOne() {
    long page = 1;
    long limit = 500;
    long total;
    do {
        Page<CallTask> taskPage = new Page<>(page, limit);
        Page<CallTask> pagResult = page(taskPage,
                Wrappers.<CallTask>lambdaQuery()
                        .eq(CallTask::getCallStatus, "WAITING")
                        .le(CallTask::getReqTime, new Date()));
        total = pagResult.getTotal();

        List<CallTask> records = pagResult.getRecords();
        if (CollUtil.isEmpty(records)) {
            break;
        }
        call(records);
        page++;

    } while (page * limit < total);
}

使用流式查询

public void methodTwo() {
    Date reqDate = new Date();
    LambdaQueryWrapper<CallTask> wrapper = Wrappers.<CallTask>lambdaQuery()
            .eq(CallTask::getCallStatus, "WAITING")
            .le(CallTask::getReqTime, reqDate);
    int batchSize = 1000;
    int offset = 0;
    while (true) {
        List<CallTask> taskList = list(wrapper.last("limit " + offset + "," + batchSize));
        if (CollUtil.isEmpty(taskList)) {
            break;
        }
        offset += batchSize;
        call(taskList);
    }
}

还有在查询的过程中减少查询字段,避免 select *

流式查询和分页查询使用场景
分页查询通常用于展示数据,如在网页上显示数据列表,需要分页展示。而流式查询通常用于数据处理,如在数据清洗、数据分析等场景中。

### 生产环境中大批量数据导入的最佳实践和方法 在生产环境中进行大批量数据导入时,需要综合考虑性能、稳定性和资源消耗等因素。以下是几种常见的最佳实践和方法: #### 1. **优化数据库操作** - 在关系型数据库(如MySQL、Oracle)中,可以使用批量插入语句来减少网络开销和事务管理的负担。例如,通过调整`innodb_buffer_pool_size`等参数[^3],可以显著提升MySQL的写入性能。 - 对于Oracle数据库,可以使用GoldenGate工具将生产环境的数据同步到测试库中,而无需直接连接两个数据库[^2]。 #### 2. **利用内存表或临时表** - 在某些场景下,可以先将数据导入到内存表或临时表中,然后再将其迁移到正式表中。这种方法可以避免锁表问题,并减少对主表的影响。 #### 3. **文件级导入** - 使用文件作为中间媒介进行数据导入是一种高效的方式。例如,在HBase中可以通过`ImportTsv`工具将CSV文件中的数据批量导入到表中[^4]。这种方式的优势在于减少了应用程序层的处理压力,直接由底层框架完成数据加载。 #### 4. **多线程与并行处理** - 多线程技术可以显著提高大批量数据的导入速度。例如,可以通过Java的线程池机制将文件拆分为多个部分,并行处理每一部分的数据[^5]。需要注意的是,线程池的大小应根据硬件资源合理配置,以避免因线程过多而导致的上下文切换开销。 #### 5. **分批处理** - 分批处理是一种常用的策略,尤其适用于内存有限或网络带宽不足的场景。例如,在使用POI库处理Excel文件时,可以通过设置`SXSSFWorkbook`来限制内存中的行数,从而避免频繁的Full GC或OOM问题[^1]。 #### 6. **预分配存储空间** - 在导入大批量数据之前,预先分配足够的存储空间可以避免动态扩展带来的性能损失。例如,在HDFS中上传大文件时,可以提前规划好输出路径和存储格式[^4]。 #### 7. **监控与日志** - 在生产环境中,实时监控数据导入过程是必不可少的。可以通过日志记录每一步的操作结果,以便快速定位潜在问题。此外,还可以设置告警机制,确保在异常情况下能够及时响应。 ```python # 示例:Python中实现分批处理的代码 def batch_process(data, batch_size): for i in range(0, len(data), batch_size): yield data[i:i + batch_size] data = [x for x in range(1000000)] for batch in batch_process(data, 1000): # 执行批量插入或其他操作 pass ``` ### 总结 在生产环境中进行大批量数据导入时,需要结合具体的业务场景和技术栈选择合适的方案。无论是优化数据库配置、采用文件级导入,还是利用多线程与分批处理,都应在实际应用中不断调整和优化,以达到最佳效果。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值