百万数据秒级导出
【百万数据秒级导出】项目模拟一百万的数据导出,要求10s内实现全部数据导出,项目使用springboot + mysql + mybatis + poi。
项目核心思想 数据分页 + 线程池
采用线程池和数据分页的原因:在于数据导出涉及IO操作,不采用线程池的话,串行耗时较长,同时数据量较大,不对数据进行分页处理,可能会产生内存溢出。
文章图片
数据来源:百万数据插入
用户数据库:
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(45) DEFAULT NULL,
`createdTime` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`updatedTime` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `index` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户测试表';
线程池数据量的设计:合理配置线程池数量
由于数据导出涉及大量IO, 故线 程数量 = 2 * cpu核数
final int nThreads = Runtime.getRuntime().availableProcessors();
ExecutorService pool = Executors.newFixedThreadPool(nThreads << 1);
同时,通过数据库查询语句添加where条件,减少数据深度分页带来的性能损耗问题
select id, `name` from user whereid > #{param1} limit 0, #{param2}
除此之外,数据库的优化,地址:MySQL核心参数优化
package com.high.concurrency.currency02.service.impl;
import com.high.concurrency.currency02.domain.User;
import com.high.concurrency.currency02.mapper.UserMapper;
import com.high.concurrency.currency02.service.IUserService;
import com.high.concurrency.currency02.util.ExcelUtil;
import com.high.concurrency.currency02.util.PageUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
public String exportData() {
// 获取可用的线程数
final int nThreads = Runtime.getRuntime().availableProcessors();
ExecutorService pool = Executors.newFixedThreadPool(nThreads << 1);
int pageSize = PageUtil.pageSize;
// 获取数据总量
Integer count = userMapper.getCount();
// 获取总页数
int totalPageCount= PageUtil.getTotalPageCount(count);
// 开始统计时间
long start=System.currentTimeMillis();
int maxId = 0;
for(int currentPageNum = 0;
currentPageNum < totalPageCount;
currentPageNum++) {
List userList = userMapper.selectPage(maxId, pageSize);
maxId = userList.get(userList.size() - 1).getId();
int finalCurrentPageNum = currentPageNum;
Runnable run = new Runnable() {
@Override
public void run() {
ExcelUtil.createExcel(finalCurrentPageNum, userList);
if(finalCurrentPageNum == (totalPageCount-1)){
System.out.println("export data to excel, ithas spent " +(System.currentTimeMillis()-start)+"ms");
}
}
};
pool.execute(run);
}
return "ok";
}
}
操作效果如下:
文章图片
代码地址:github
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- 即将到手三百万
- 使用协程爬取网页,计算网页数据大小
- Java|Java基础——数组
- Python数据分析(一)(Matplotlib使用)
- Jsr303做前端数据校验
- Spark|Spark 数据倾斜及其解决方案
- 数据库设计与优化
- 爬虫数据处理HTML转义字符
- 数据库总结语句