欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用EasyExcel實(shí)現(xiàn)百萬級別數(shù)據(jù)導(dǎo)出的代碼示例

 更新時(shí)間:2023年12月04日 10:37:16   作者:shark_chili  
近期需要開發(fā)一個(gè)將百萬數(shù)據(jù)量MySQL8的數(shù)據(jù)導(dǎo)出到excel的功能,所以本文講給大家介紹了基于EasyExcel實(shí)現(xiàn)百萬級別數(shù)據(jù)導(dǎo)出,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下

前言

近期需要開發(fā)一個(gè)將百萬數(shù)據(jù)量MySQL8的數(shù)據(jù)導(dǎo)出到excel的功能,查閱相關(guān)資料后便整理了這篇實(shí)現(xiàn)方案供讀者參考。

需求簡述

該數(shù)據(jù)表是一張用戶表,包含idname,該用戶表數(shù)據(jù)量在300w左右,以自增id作為主鍵,而功能要求我們在一分鐘之內(nèi)完成百萬數(shù)據(jù)導(dǎo)出到excel。需要注意的是,我們導(dǎo)出的excel格式為xlsx,它的每一個(gè)sheet只能容納100w的數(shù)據(jù),這也就意味著我們的數(shù)據(jù)必須以100w作為批次寫到不同的sheet中。

實(shí)現(xiàn)思路

我們先來說說需要解決的問題:

  • 如果一次性查詢300w左右的數(shù)據(jù)可能會(huì)占據(jù)大量的內(nèi)存,如果對象字段很多的情況下,很可能出現(xiàn)內(nèi)存溢出,我們要如何解決?
  • 每個(gè)excel文件都有sheet,并且每個(gè)sheet只能容納100w左右的數(shù)據(jù),對于這個(gè)問題我們要如何解決?
  • 數(shù)據(jù)寫入到excel時(shí),有沒有合適的工具推薦?

對于問題1我們采用分頁查詢的方式進(jìn)行查詢,參考自己堆內(nèi)存的配置推算每次分頁查詢的數(shù)據(jù)量。因?yàn)閱栴}1采用了分頁查詢,我們完全可以通過分頁查詢的次數(shù)推算出一個(gè)sheet寫入了多少數(shù)據(jù),例如我們每次分頁查詢50w的數(shù)據(jù),那么每兩次就可以視為一個(gè)sheet寫滿了,我們就可以創(chuàng)建一個(gè)新的sheet寫入數(shù)據(jù)。

這里需要注意一點(diǎn),因?yàn)槲覀兎猪摬樵兠鎸Φ氖前偃f級別的數(shù)據(jù),所以隨著分頁的推進(jìn)勢必出現(xiàn)深分頁導(dǎo)致查詢效率勢降低,所以為了提高分頁查詢的效率,我們可以利用查詢數(shù)據(jù)有序的特性,通過id作為偏移進(jìn)行分頁查詢。

例如我們第一次分頁查詢的sql語句為:

select * from t_user limit 500000 ; 

假如我們不以id作為索引,那么第二次的分頁查詢sql則是:

 select * from t_user limit 500000,500000 ; 

查看該查詢執(zhí)行計(jì)劃,可以看到該查詢一次性查詢到幾乎全表的數(shù)據(jù),并且還走了全秒掃描性能可想而知:

id|select_type|table |partitions|type|possible_keys|key|key_len|ref|rows   |filtered|Extra|
--+-----------+------+----------+----+-------------+---+-------+---+-------+--------+-----+
 1|SIMPLE     |t_user|          |ALL |             |   |       |   |2993040|   100.0|     |

因?yàn)槲覀兊臄?shù)據(jù)表是id自增的,所以我們查詢的時(shí)候完全可以基于該特性通過上一次查詢到的id作為篩選條件進(jìn)行分頁查詢。

所以我們的分頁查詢可直接改為:

select * from t_user where id > 500000 limit 500000 ; 

再次查看執(zhí)行計(jì)劃可以發(fā)現(xiàn)該查詢?yōu)榉秶樵?,查詢到的?shù)據(jù)量也少了很多,性能顯著提升:

id|select_type|table |partitions|type |possible_keys|key    |key_len|ref|rows   |filtered|Extra      |
--+-----------+------+----------+-----+-------------+-------+-------+---+-------+--------+-----------+
 1|SIMPLE     |t_user|          |range|PRIMARY      |PRIMARY|8      |   |1496520|   100.0|Using where|

因?yàn)槭忻嫔媳容^多的excel導(dǎo)出工具,常見的就是Apache poi,但是它們的操作對于內(nèi)存的消耗非常嚴(yán)重,對于我們這種大數(shù)據(jù)量的寫入不是很友好,所以筆者更推薦使用阿里的EasyExcel,它對poi進(jìn)行一定的封裝和優(yōu)化,同等數(shù)據(jù)量寫入使用的內(nèi)存更小。

解決上述問題之后,我們就可以說說代碼實(shí)現(xiàn)思路了,以本文示例來說,有一張用戶表有300w左右的數(shù)據(jù),每次查詢時(shí)只需查詢id(4字節(jié))name(10字節(jié)),按照64位的操作系統(tǒng)來說,一個(gè)user對象所占用的內(nèi)存大小為:

object header +pointer+id字段+name字段大小=8+8+4+10=30字節(jié)

因?yàn)?code>java對象內(nèi)存大小需要16位對齊,需要補(bǔ)齊2個(gè)字節(jié),所以實(shí)際大小為32字節(jié),按照筆者對于堆內(nèi)存的配置,每次查詢50w條數(shù)據(jù)是允許的,所以每次從數(shù)據(jù)庫讀取數(shù)據(jù)并轉(zhuǎn)為java對象,也只需要32*500000/102415M內(nèi)存即可。

確定每次分頁查詢50w條數(shù)據(jù)之后,我們就需要確定一共需要查詢幾個(gè)分頁,然后就可以根據(jù)pageSize確定查詢的頁數(shù)。
因?yàn)槊看尾樵?code>50w條數(shù)據(jù),所以每兩次完成分頁查詢和寫入基本上一個(gè)sheet就會(huì)滿了,這時(shí)候我們就需要?jiǎng)?chuàng)建一個(gè)新的sheet進(jìn)行數(shù)據(jù)寫入了。

總結(jié)一下實(shí)現(xiàn)步驟:

  • 查詢目標(biāo)數(shù)據(jù)量大小。
  • 根據(jù)每次分頁大小確定查詢頁數(shù)。
  • 根據(jù)頁數(shù)大小進(jìn)行遍歷,進(jìn)行分頁查詢,并將數(shù)據(jù)寫入到文件中。
  • 基于頁數(shù)確定sheet切換時(shí)機(jī)。

代碼示例

以下便是筆者基于上述思路所實(shí)現(xiàn)的代碼,查看日志也可以發(fā)現(xiàn)50w的數(shù)據(jù)查詢和寫入加起來只需6s。最終執(zhí)行耗時(shí)也只需45s

public static void main(String[] args) {
        SpringApplication app = new SpringApplication(WebApplication.class);
        Environment env = app.run(args).getEnvironment();
        logger.info("啟動(dòng)成功??!");
        logger.info("地址: \thttp://127.0.0.1:{}", env.getProperty("server.port"));

        TUserMapper userMapper = SpringUtil.getBean(TUserMapper.class);
        //計(jì)算總的數(shù)據(jù)量
        int count = (int) userMapper.countByExample(null);


        //獲取分頁總數(shù)
        int queryCount = 50_0000;
        int pageCount = count % queryCount == 0 ? count / queryCount : count / queryCount + 1;

        //設(shè)置導(dǎo)出的文件名
        String fileName = "result.xlsx";
        //設(shè)置excel的sheet號碼
        int sheetNo = 1;
        //設(shè)置第一個(gè)sheet的名字
        String sheetName = "sheet-" + sheetNo;


        long start = System.currentTimeMillis();
        // 創(chuàng)建writeSheet
        WriteSheet writeSheet = EasyExcel.writerSheet(sheetNo, sheetName).build();
        //記錄每次分頁查詢的最大值
        Long maxId = null;

        //指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, TUser.class).build()) {
            //寫入每一頁分頁查詢的數(shù)據(jù)
            for (int i = 1; i <= pageCount; i++) {
                // 分頁去數(shù)據(jù)庫查詢數(shù)據(jù) 這里可以去數(shù)據(jù)庫查詢每一頁的數(shù)據(jù)
                long queryStart = System.currentTimeMillis();
                TUserExample userExample = new TUserExample();


                //如果是第一次則直接進(jìn)行分頁查詢,反之基于上一次分頁查詢的分頁定位實(shí)際偏移量,篩選前n條數(shù)據(jù)以達(dá)到分頁效果
                if (i == 1) {
                    PageHelper.startPage(i, queryCount, false);
                } else if (maxId != null) {
                    userExample.createCriteria().andIdGreaterThan(maxId);
                    PageHelper.startPage(0, queryCount, false);
                }


                List<TUser> userList = userMapper.selectByExample(userExample);
                //更新下一次分頁查詢用的id
                if (CollUtil.isNotEmpty(userList)) {
                    maxId = userList.get(userList.size() - 1).getId();
                }

                long queryEnd = System.currentTimeMillis();
                logger.info("數(shù)據(jù)大小:{},寫入sheet位置:{},耗時(shí):{}", userList.size(), sheetName, queryEnd - queryStart);

                long writeStart = System.currentTimeMillis();
                excelWriter.write(userList, writeSheet);

                long writeEnd = System.currentTimeMillis();
                logger.info("本次寫入耗時(shí):{}", writeEnd - writeStart);

                //如果% 2 == 0,則說明一個(gè)sheet寫入了50*2即100w的數(shù)據(jù),需要?jiǎng)?chuàng)建新的sheet進(jìn)行寫入
                if (i % 2 == 0) {
                    sheetName = "sheet-" + (++sheetNo);
                    writeSheet = EasyExcel.writerSheet(sheetNo, sheetName).build();
                    logger.info("寫滿一個(gè)sheet,切換到下一個(gè)sheet:{}", sheetName);
                }
            }
        }
        long total = System.currentTimeMillis() - start;
        logger.info("導(dǎo)出結(jié)束,總耗時(shí):{}", total);

    }

可能會(huì)有讀者好奇筆者這個(gè)50w的數(shù)值設(shè)計(jì)思路是什么,除了考慮避免OOM以外,還考慮到每個(gè)sheet只能寫入100w條的數(shù)據(jù),為了方便通過分頁查詢的輪次確定當(dāng)前寫入的數(shù)據(jù)量大小,筆者嘗試過20w、50w。
最終在壓測結(jié)果上看出,50w讀寫耗時(shí)雖然是20w的2倍,但是IO次數(shù)卻不到20w查詢的二分之一,通過更少的IO操作獲得更好的執(zhí)行性能。

# 50w的讀寫耗時(shí)
com.sharkChili.webTemplate.config.WebApplication  :73   [32m                  [0;39m 數(shù)據(jù)大小:500000,寫入sheet位置:sheet-1,耗時(shí):4719
2023-12-03 10:13:58.675 INFO  com.sharkChili.webTemplate.config.WebApplication  :78   [32m                  [0;39m 本次寫入耗時(shí):2911
2023-12-03 10:14:02.517 INFO  com.sharkChili.webTemplate.config.WebApplication  :73   [32m                  [0;39m 數(shù)據(jù)大小:500000,寫入sheet位置:sheet-1,耗時(shí):3841
2023-12-03 10:14:04.860 INFO  com.sharkChili.webTemplate.config.WebApplication  :78   [32m                  [0;39m 本次寫入耗時(shí):2343

小結(jié)

以上便是筆者的百萬級別數(shù)據(jù)導(dǎo)出的落地方案,可以看出筆者著重在分頁查詢大小和分頁查詢sql上進(jìn)行重點(diǎn)優(yōu)化,通過平衡分頁查詢的數(shù)據(jù)量和IO次數(shù)找到合適的pageSize,再通過上一次分頁查詢結(jié)果定位下一次查詢的id作為where條件,避免分頁查詢時(shí)的全秒掃描以得到符合業(yè)務(wù)需求的高性能sql,從而完成百萬級別數(shù)據(jù)的高效導(dǎo)出。

以上就是使用EasyExcel實(shí)現(xiàn)百萬級別數(shù)據(jù)導(dǎo)出的代碼示例的詳細(xì)內(nèi)容,更多關(guān)于EasyExcel實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java?基于Hutool實(shí)現(xiàn)DES加解密示例詳解

    Java?基于Hutool實(shí)現(xiàn)DES加解密示例詳解

    這篇文章主要介紹了Java基于Hutool實(shí)現(xiàn)DES加解密,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • SpringBoot的監(jiān)控(Actuator)功能用法詳解

    SpringBoot的監(jiān)控(Actuator)功能用法詳解

    這篇文章主要介紹了SpringBoot的監(jiān)控(Actuator)功能用法,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • 都9102年了,你還用for循環(huán)操作集合嗎

    都9102年了,你還用for循環(huán)操作集合嗎

    這篇文章主要給大家介紹了關(guān)于java中for循環(huán)操作集合使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • JDBC查詢Map轉(zhuǎn)對象實(shí)現(xiàn)過程詳解

    JDBC查詢Map轉(zhuǎn)對象實(shí)現(xiàn)過程詳解

    這篇文章主要介紹了JDBC查詢Map轉(zhuǎn)對象實(shí)現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Java微服務(wù)Nacos Config配置中心超詳細(xì)講解

    Java微服務(wù)Nacos Config配置中心超詳細(xì)講解

    配置文件相對分散。在一個(gè)微服務(wù)架構(gòu)下,配置文件會(huì)隨著微服務(wù)的增多變的越來越多,而且分散 在各個(gè)微服務(wù)中,不好統(tǒng)一配置和管理。每一個(gè)環(huán)境所使用的配置理論上都是不同的,一旦需要修改,就需要我們?nèi)ジ鱾€(gè)微服務(wù)下手動(dòng)維護(hù)
    2023-02-02
  • 樹,二叉樹(完全二叉樹,滿二叉樹)概念圖解

    樹,二叉樹(完全二叉樹,滿二叉樹)概念圖解

    今天小編就為大家分享一篇關(guān)于二叉樹的圖文詳解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧,希望能對你有所幫助
    2021-07-07
  • java基礎(chǔ)開發(fā)泛型類的詳解

    java基礎(chǔ)開發(fā)泛型類的詳解

    這篇文章為大家介紹了java基礎(chǔ)開發(fā)中泛型類的詳解,包括泛型的概念以及應(yīng)用實(shí)例有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-10-10
  • SpringMVC Tomcat控制臺(tái)亂碼問題解決方案

    SpringMVC Tomcat控制臺(tái)亂碼問題解決方案

    這篇文章主要介紹了SpringMVC Tomcat控制臺(tái)亂碼問題解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • 淺談hibernate中對象的3種狀態(tài)_瞬時(shí)態(tài)、持久態(tài)、脫管態(tài)

    淺談hibernate中對象的3種狀態(tài)_瞬時(shí)態(tài)、持久態(tài)、脫管態(tài)

    下面小編就為大家?guī)硪黄獪\談hibernate中對象的3種狀態(tài)_瞬時(shí)態(tài)、持久態(tài)、脫管態(tài)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-08-08
  • Spring Boot中自動(dòng)化配置的利弊以及解決方法

    Spring Boot中自動(dòng)化配置的利弊以及解決方法

    這篇文章主要給大家介紹了關(guān)于Spring Boot中自動(dòng)化配置的利弊以及解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。
    2017-08-08

最新評論