导出任务耗时怎么优化?

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 当处理大量数据的导入导出时,需避免长时间阻塞用户界面。推荐采用异步任务处理方式,提交任务后后台线程执行数据处理。对于导出功能,设计前端界面包括“导出”与“导出记录”按钮;导出记录包含批次号、时间、导出URL等字段。后端生成Excel文件并上传至服务器,记录URL以便下载。导入功能类似,记录批次号、总条数、成功条数等信息。为避免大量数据查询导致内存溢出或系统响应缓慢,应使用分批处理策略,例如分页查询来减轻MySQL内存负担。提供了Java工具类实现分页查询和处理逻辑

大量数据的导入导出时,请求一定非常耗时,页面一定会不停转圈圈,不可能让用户一直停留在这个页面转圈圈,这样并不友好。

比较好的方式就事通过异步的方式,先提交任务,然后通过线程的处理数据。一次性如果导出大量数据时,需要批量查询结果到处。

导出功能设计:

前端页面设计如下: 新增 导出按钮 和导出记录按钮 导出记录页面字段如下: 批次号  时间  导出URL  操作(导出) 后端表结构

sql

代码解读

复制代码

create table export_record(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
 `batch_no` varchar(32)  DEFAULT NULL COMMENT '导入批次号',
 `export_type` varchar(3) DEFAULT NULL COMMENT '类型(1:订单导出)',
  `export_url` varchar(300) DEFAULT NULL COMMENT 'url',
 `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期',
  `create_code` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人代码',
  `create_name` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人名称',
  `last_update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  `last_update_code` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最后更新人',
  `last_update_name` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最后更新人',
   PRIMARY KEY (`id`),
   KEY `index_import_record` (`batch_no`) USING BTREE
)ENGINE=InnoDB  COMMENT='导出记录';

后端功能逻辑: 将导出的数据生成excel文件,并上传到服务器中,上传的文件生产的url保存记录,供前端页面下载excel文件

导入功能设计

前端页面设计如下:导入记录页面字段如下: 批次号  时间  总条数 成功条数 操作

sql

代码解读

复制代码

create table import_record (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
 `batch_no` varchar(32)  DEFAULT NULL COMMENT '导入批次号',
 `export_type` varchar(3) DEFAULT NULL COMMENT '类型(1:订单导出)',
  `total_num` int DEFAULT 0 COMMENT '总数量',
  `success_num` int DEFAULT 0 COMMENT '成功数量',
 `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期',
  `create_code` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人代码',
  `create_name` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人名称',
  `last_update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  `last_update_code` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最后更新人',
  `last_update_name` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最后更新人',
   PRIMARY KEY (`id`),
   KEY `index_import_record` (`batch_no`) USING BTREE
)ENGINE=InnoDB  COMMENT='导入记录';


create table import_record (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
 `batch_no` varchar(32)  DEFAULT NULL COMMENT '导入批次号',
 `export_type` varchar(3) DEFAULT NULL COMMENT '类型(1:订单导出)',
  `total_num` int DEFAULT 0 COMMENT '总数量',
  `success_num` int DEFAULT 0 COMMENT '成功数量',
 `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期',
  `create_code` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人代码',
  `create_name` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人名称',
  `last_update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  `last_update_code` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最后更新人',
  `last_update_name` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最后更新人',
   PRIMARY KEY (`id`),
   KEY `index_import_record` (`batch_no`) USING BTREE
)ENGINE=InnoDB  COMMENT='导入记录';

导入逻辑: 对导入的excel文件进行解析,并保存解析出来的数据。

大量数据查询拆分成批量任务查询

导出数据可能会导出大量数据,通常情况下,一次性查询大量数据导致负载压力的原因是在一次查询中同时检索了太多数据,并在内存中进行处理,这会占用大量系统资源,造成系统响应变慢和崩溃等问题。

mysql会将检索出来的数据都缓存到内存,一次性返回到服务端,这样会占用大量的内存资源,导致内存不足,从而影响系统运行稳定性。

解决的方式是批次处理,如分页查询数据,从而减少mysql查询占用的内存。

分页查询工具如下:

java

代码解读

复制代码

@CustomLog
public class PageBigDataUtil {

    /**
     * @param queryParam 查询条件
     * @param function 分页查询
     * @return
     */
    public static <T> List<T> pageBigData(T queryParam, Function<PageQueryBean<T>, PageQueryBean<T>> function) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        List<T> results = new ArrayList<>();
        int size = 500;
        int current = 1;
        for (; ; ) {
            PageQueryBean<T> pageParam = new PageQueryBean<>();
            SimplePage<T> simplePage = new SimplePage<>();
            simplePage.setPageSize(size);
            simplePage.setPageNum(current);
            pageParam.setPage(simplePage);
            pageParam.setParameter(queryParam);
            PageQueryBean<T> result = function.apply(pageParam);
            logger.keyword("分页批次任务").info("--------导入开始,本批次共:{} 轮,当前第{}轮", result.getPage().getPages(), current);
            if (DataUtil.isEmpty(result)) {
                break;
            }
            results.addAll(result.getPage().getList());
            if ((long) size * current >= result.getPage().getTotal()) {
                break;
            }
            current++;
        }
        stopWatch.stop();
        logger.info("批次任务已结束,分页批次任务:{},获取总记录数:{},总耗时:{}秒", current, results.size(), stopWatch.getTotalTimeSeconds());
        return results;
    }

    /**
     *
     * @param queryParam 查询条件
     * @param function 分页查询
     * @param consumer 批次消费
     * @param <T>
     */
    public static <T> void handleBigData(T queryParam, Function<PageQueryBean<T>, PageQueryBean<T>> function, Consumer<List<T>> consumer) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        int size = 500;
        int current = 1;
        for (; ; ) {
            PageQueryBean<T> pageParam = new PageQueryBean<>();
            SimplePage<T> simplePage = new SimplePage<>();
            simplePage.setPageSize(size);
            simplePage.setPageNum(current);
            pageParam.setPage(simplePage);
            pageParam.setParameter(queryParam);
            PageQueryBean<T> result = function.apply(pageParam);
            if (DataUtil.isEmpty(result)) {
                break;
            }
            // 业务处理
            consumer.accept(result.getPage().getList());
            logger.keyword("分页批次任务").info("--------导入开始,本批次共:{} 轮,当前第{}轮", result.getPage().getPages(), current);
            if ((long) size * current >= result.getPage().getTotal()) {
                break;
            }
            current++;
        }
        stopWatch.stop();
        logger.info("批次任务已结束,总耗时:{}秒", stopWatch.getTotalTimeSeconds());
    }
}


转载来源:https://juejin.cn/post/7367975773519200307

相关文章
|
6月前
|
缓存 关系型数据库 MySQL
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
293 0
|
4月前
|
SQL 前端开发 关系型数据库
导出任务耗时如何优化
大量数据的导入导出时,请求一定非常耗时,页面一定会不停转圈圈,不可能让用户一直停留在这个页面转圈圈,这样并不友好。 比较好的方式就事通过异步的方式,先提交任务,然后通过线程的处理数据。一次性如果导出大量数据时,需要批量查询结果到处。
|
6月前
|
小程序 算法 Android开发
启动耗时计算模型优化说明
启动耗时计算模型优化说明
52 11
|
数据可视化 测试技术
JMeter 中如何准确设置并发量
JMeter 是一个功能强大的性能测试工具,可以模拟许多用户同时访问应用程序的情况。在使用 JMeter 进行性能测试时,设置并发是非常重要的。本文将介绍如何在 JMeter 中设置并发和查看报告。
JMeter 中如何准确设置并发量
|
JavaScript 前端开发 API
【项目数据优化三】长列表数据优化
【项目数据优化三】长列表数据优化
131 0
|
SQL NoSQL 关系型数据库
使用 查询分离 后 从20s优化到500ms
使用 查询分离 后 从20s优化到500ms
|
JavaScript Go
埋点统计优化,首屏加载速度提升
埋点统计在我们业务里经常有遇到,或者很普遍的,我们自己网站也会加入第三方统计,我们会看到动态加载方式去加载jsdk,也就是你常常看到的insertBefore操作,我们很少考虑到为什么这么做,直接同步加载不行吗?统计代码会影响业务首屏加载吗?同步引入方式,当然会,我的业务代码还没加载,首屏就加载一大段统计的jsdk,在移动端页面打开要求比较高的苛刻条件下,首屏优化,你可以在埋点统计上做些优化,那么页面加载会有一个很大的提升
175 0
埋点统计优化,首屏加载速度提升
|
SQL 关系型数据库 MySQL
一次SQL查询优化原理分析:900W+数据,从17s到300ms
一次SQL查询优化原理分析:900W+数据,从17s到300ms
一次SQL查询优化原理分析:900W+数据,从17s到300ms
|
Java
这4种方式,统计代码执行耗时,才足够优雅!
这4种方式,统计代码执行耗时,才足够优雅!
336 0
如何优雅的统计代码耗时?
代码耗时统计在日常开发中算是一个十分常见的需求,特别是在需要找出代码性能瓶颈时。 可能也是受限于 Java 的语言特性,总觉得代码写起来不够优雅,大量的耗时统计代码,干扰了业务逻辑。特别是开发功能的时候,有个感受就是刚刚开发完代码很清爽优雅,结果加了一大堆辅助代码后,整个代码就变得臃肿了,自己看着都挺难受。因此总想着能不能把这块写的更优雅一点,今天本文就尝试探讨下“代码耗时统计”这一块。
230 0
下一篇
无影云桌面