最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java中大數(shù)據(jù)量批處理的性能優(yōu)化指南

 更新時(shí)間:2026年07月07日 08:50:16   作者:霸道流氓氣質(zhì)  
批處理的核心原則是減少網(wǎng)絡(luò)和SQL解析開(kāi)銷(xiāo),本文總結(jié)了Java大數(shù)據(jù)量批處理性能優(yōu)化的核心方法,重點(diǎn)對(duì)比了不同方案的效率差異,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下

一、批處理核心原則

  • 單條處理:20萬(wàn)次網(wǎng)絡(luò)往返 + 20萬(wàn)次SQL解析 = 極慢
  • 批量處理:200次網(wǎng)絡(luò)往返 + 200次SQL解析 = 快1000倍

批次大小選擇平衡點(diǎn)

  • 太小(1~100):網(wǎng)絡(luò)往返次數(shù)多,總耗時(shí)長(zhǎng)
  • 太大(10000+):?jiǎn)未蝺?nèi)存占用高,SQL 過(guò)長(zhǎng),數(shù)據(jù)庫(kù)鎖持有時(shí)間長(zhǎng)
  • 最佳區(qū)間:1000~5000

二、導(dǎo)入優(yōu)化

2.1 批量 INSERT(核心)

// 慢:逐條 INSERT(20萬(wàn)次網(wǎng)絡(luò)IO)
for (Entity e : list) {
    mapper.insert(e);  // 每條一次網(wǎng)絡(luò)往返
}

// 快:批量 INSERT(100次網(wǎng)絡(luò)IO)
for (int i = 0; i < list.size(); i += 2000) {
    int end = Math.min(i + 2000, list.size());
    mapper.batchInsert(list.subList(i, end));
}

XML:

<insert id="batchInsert">
  INSERT INTO my_table (col1, col2, col3) VALUES
  <foreach collection="list" item="item" separator=",">
    (#{item.col1}, #{item.col2}, #{item.col3})
  </foreach>
</insert>

2.2 分頁(yè)處理模式

int pageSize = 2000;
int page = 1;
while (true) {
    List<Detail> batch = mapper.selectPage(taskId, page, pageSize);
    if (batch.isEmpty()) break;

    // 校驗(yàn)或入庫(kù)
    processBatch(batch);

    // 回寫(xiě)狀態(tài)
    mapper.batchUpdateStatus(batch);
    page++;
}

2.3 完整導(dǎo)入示例

public void importData(List<RawData> allData) {
    int batchSize = 2000;
    int total = allData.size();

    for (int i = 0; i < total; i += batchSize) {
        int end = Math.min(i + batchSize, total);
        List<RawData> batch = allData.subList(i, end);

        // 1. 校驗(yàn)
        List<Entity> validList = new ArrayList<>();
        for (RawData raw : batch) {
            if (validate(raw)) {
                validList.add(convert(raw));
            }
        }

        // 2. 批量入庫(kù)
        if (!validList.isEmpty()) {
            mapper.batchInsert(validList);
        }
    }
}

三、導(dǎo)出優(yōu)化

3.1 SXSSFWorkbook 流式寫(xiě)入

SXSSFWorkbook workbook = new SXSSFWorkbook(200); // 窗口200行
Sheet sheet = workbook.createSheet("數(shù)據(jù)");

int pageSize = 5000;
int page = 1;
int rowIndex = 1;

while (true) {
    List<Entity> batch = mapper.selectPage(page, pageSize);
    if (batch.isEmpty()) break;

    for (Entity e : batch) {
        Row row = sheet.createRow(rowIndex++);
        row.createCell(0).setCellValue(e.getName());
        row.createCell(1).setCellValue(e.getAmount());
    }
    page++;
}

workbook.write(outputStream);
workbook.dispose(); // 清理臨時(shí)文件

3.2 EasyExcel 流式寫(xiě)入

ExcelWriter writer = EasyExcelFactory.write(outputStream, ExportRow.class).build();
WriteSheet sheet = EasyExcelFactory.writerSheet("數(shù)據(jù)").build();

int page = 0;
while (true) {
    List<ExportRow> batch = queryData(param, page, 5000);
    if (batch.isEmpty()) break;
    writer.write(batch, sheet);
    page++;
}
writer.finish();

四、方案對(duì)比

導(dǎo)入方案

方案20萬(wàn)條耗時(shí)內(nèi)存復(fù)雜度
逐條INSERT15~30分鐘
批量INSERT(2000/批)5~10秒中(25MB)
MQ異步+批量INSERT5~10秒(用戶(hù)無(wú)感)
線(xiàn)程池異步+批量INSERT3~8秒(用戶(hù)無(wú)感)

導(dǎo)出方案

方案20萬(wàn)條耗時(shí)內(nèi)存文件大小
XSSFWorkbook全內(nèi)存15~25秒400MB+8~12MB
SXSSFWorkbook+分頁(yè)查4~8秒20~30MB8~12MB
EasyExcel流式3~6秒15~25MB8~12MB
CSV1~3秒<5MB15~20MB

五、完整示例

@Service
public class BatchProcessService {

  @Resource
  private DataMapper dataMapper;

  private static final int BATCH_SIZE = 2000;

  /** 批量導(dǎo)入. */
  public void batchImport(List<ImportRow> rawData) {
    List<DataEntity> buffer = new ArrayList<>(BATCH_SIZE);

    for (ImportRow raw : rawData) {
      if (!validate(raw)) continue;
      buffer.add(convert(raw));

      if (buffer.size() >= BATCH_SIZE) {
        dataMapper.batchInsert(buffer);
        buffer.clear();
      }
    }
    // 處理剩余
    if (!buffer.isEmpty()) {
      dataMapper.batchInsert(buffer);
    }
  }

  /** 流式導(dǎo)出. */
  public void streamExport(OutputStream out, QueryParam param) {
    SXSSFWorkbook wb = new SXSSFWorkbook(200);
    try {
      Sheet sheet = wb.createSheet("導(dǎo)出數(shù)據(jù)");
      writeHeader(sheet);

      int page = 1, rowIdx = 1;
      while (true) {
        List<DataEntity> batch = dataMapper.selectByPage(param, page, 5000);
        if (batch.isEmpty()) break;
        for (DataEntity e : batch) {
          writeRow(sheet, rowIdx++, e);
        }
        page++;
      }
      wb.write(out);
    } finally {
      wb.dispose();
    }
  }
}

六、關(guān)鍵配置

# 數(shù)據(jù)庫(kù)連接池(批處理需要足夠連接)
spring:
  datasource:
    hikari:
      maximum-pool-size: 20      # 并發(fā)批處理時(shí)需要足夠連接
      connection-timeout: 30000
# MySQL批量操作需要的URL參數(shù)
      url: jdbc:mysql://host:3306/db?rewriteBatchedStatements=true
      #                               ↑ 關(guān)鍵!開(kāi)啟批量重寫(xiě),性能提升5~10倍

rewriteBatchedStatements=true 讓 JDBC 驅(qū)動(dòng)將多條 INSERT 合并為一條多值 INSERT,是批量寫(xiě)入性能的關(guān)鍵配置。

七、批量 UPDATE 優(yōu)化

7.1 逐條 vs 批量

// 慢:逐條UPDATE
for (Detail d : list) {
    mapper.updateStatus(d.getId(), d.getStatus());  // 20萬(wàn)次
}

// 快:CASE WHEN 批量UPDATE
mapper.batchUpdateStatus(list);  // 按批次執(zhí)行

XML 實(shí)現(xiàn):

<update id="batchUpdateStatus">
  UPDATE exchange_task_detail
  SET status = CASE id
  <foreach collection="list" item="item">
    WHEN #{item.id} THEN #{item.status}
  </foreach>
  END,
  data = CASE id
  <foreach collection="list" item="item">
    WHEN #{item.id} THEN #{item.data}
  </foreach>
  END
  WHERE id IN
  <foreach collection="list" item="item" open="(" separator="," close=")">
    #{item.id}
  </foreach>
</update>

7.2 另一種方式:臨時(shí)表 JOIN UPDATE

數(shù)據(jù)量極大時(shí)(50萬(wàn)+),可以先把要更新的數(shù)據(jù)寫(xiě)入臨時(shí)表,再用 JOIN UPDATE:

-- 1. 創(chuàng)建臨時(shí)表并插入數(shù)據(jù)
CREATE TEMPORARY TABLE tmp_update (id INT, status INT);
INSERT INTO tmp_update VALUES (1, 3), (2, 3), ...;
-- 2. JOIN UPDATE
UPDATE exchange_task_detail d
JOIN tmp_update t ON d.id = t.id
SET d.status = t.status;

八、并發(fā)批處理

8.1 線(xiàn)程池分片

public void parallelImport(List<Entity> allData) {
    int batchSize = 2000;
    ExecutorService executor = Executors.newFixedThreadPool(4);
    List<Future<?>> futures = new ArrayList<>();

    for (int i = 0; i < allData.size(); i += batchSize) {
        int start = i;
        int end = Math.min(i + batchSize, allData.size());
        List<Entity> batch = allData.subList(start, end);

        futures.add(executor.submit(() -> {
            mapper.batchInsert(batch);
        }));
    }

    // 等待全部完成
    for (Future<?> f : futures) {
        f.get();
    }
    executor.shutdown();
}

注意:并發(fā)寫(xiě)入時(shí)需確保沒(méi)有唯一鍵沖突,且數(shù)據(jù)庫(kù)連接池大小 >= 線(xiàn)程數(shù)。

8.2 性能對(duì)比

方式20萬(wàn)條INSERT耗時(shí)數(shù)據(jù)庫(kù)連接占用
單線(xiàn)程 2000/批5~10秒1個(gè)
4線(xiàn)程 2000/批2~4秒4個(gè)
8線(xiàn)程 2000/批1~2.5秒8個(gè)

九、內(nèi)存優(yōu)化技巧

9.1 避免全量加載

// 錯(cuò)誤:20萬(wàn)條全加載到內(nèi)存
List<Entity> all = mapper.selectAll();  // ~200MB

// 正確:分頁(yè)加載,用完即棄
int page = 1;
while (true) {
    List<Entity> batch = mapper.selectPage(page, 5000);  // ~5MB
    if (batch.isEmpty()) break;
    process(batch);
    page++;
    // batch 在下一次循環(huán)時(shí)可被GC
}

9.2 流式讀取 Excel(導(dǎo)入時(shí))

// EasyExcel 流式讀取,不會(huì)把20萬(wàn)行全加載到內(nèi)存
EasyExcelFactory.read(inputStream, RowData.class,
    new PageReadListener<RowData>(batch -> {
        // 每100行回調(diào)一次,處理后釋放
        processBatch(batch);
    }, 100))
    .sheet()
    .doRead();

十、rewriteBatchedStatements詳解

這是 MySQL JDBC 驅(qū)動(dòng)的關(guān)鍵參數(shù):jdbc:mysql://host:3306/db?rewriteBatchedStatements=true

設(shè)置實(shí)際執(zhí)行的SQL網(wǎng)絡(luò)往返
false(默認(rèn))INSERT INTO t VALUES(1); INSERT INTO t VALUES(2); ...N次
trueINSERT INTO t VALUES(1),(2),(3),...1次

對(duì)批量 INSERT 性能提升 5~10倍。對(duì) UPDATE 也有類(lèi)似優(yōu)化(合并為多條一次發(fā)送)。

十一、總結(jié)

導(dǎo)入優(yōu)化公式:

  • 總耗時(shí) = (數(shù)據(jù)量 / 批次大小) × 單批耗時(shí)
  • 單批耗時(shí) = 網(wǎng)絡(luò)往返(~2ms) + SQL執(zhí)行(~5ms) + 業(yè)務(wù)處理(~1ms)
  • 20萬(wàn)條 / 2000批 × 8ms = 0.8秒(理想值)
  • 實(shí)際加上GC、連接等待等 ≈ 5~10秒

導(dǎo)出優(yōu)化公式:

  • 總耗時(shí) = 查詢(xún)時(shí)間 + 寫(xiě)入時(shí)間
  • 查詢(xún) = (數(shù)據(jù)量 / 頁(yè)大小) × 單頁(yè)查詢(xún)時(shí)間
  • 寫(xiě)入 = SXSSFWorkbook 流式寫(xiě)(不受內(nèi)存限制)

批次大小選擇:

  ┌─────────────┬──────────────────────────────┐
  │ 數(shù)據(jù)量      │ 推薦批次大小                   │
  ├─────────────┼──────────────────────────────┤
  │ < 1萬(wàn)       │ 1000                         │
  │ 1萬(wàn)~10萬(wàn)    │ 2000                         │
  │ 10萬(wàn)~50萬(wàn)   │ 2000~5000                    │
  │ > 50萬(wàn)      │ 5000 + 并發(fā)線(xiàn)程池             │
  └─────────────┴──────────────────────────────┘

以上就是Java中大數(shù)據(jù)量批處理的性能優(yōu)化指南的詳細(xì)內(nèi)容,更多關(guān)于Java大數(shù)據(jù)處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java工程師面試題一面二面整理

    Java工程師面試題一面二面整理

    在本篇文章里小編給大家整理的是關(guān)于Java 工程師面試題的相關(guān)知識(shí)點(diǎn),有需要的可以參考下。
    2019-08-08
  • SpringBoot中@EnableAutoConfiguration和@Configuration的區(qū)別

    SpringBoot中@EnableAutoConfiguration和@Configuration的區(qū)別

    這篇文章主要介紹了SpringBoot中@EnableAutoConfiguration和@Configuration的區(qū)別,@SpringBootApplication相當(dāng)于@EnableAutoConfiguration,@ComponentScan,@Configuration三者的集合,需要的朋友可以參考下
    2023-08-08
  • java中spi使用詳解

    java中spi使用詳解

    java中spi(service provider interface)是jdk內(nèi)置的一種服務(wù)發(fā)現(xiàn)機(jī)制,可以基于配置,在運(yùn)行時(shí)加載指定服務(wù)。這篇文章主要介紹了java中spi使用,需要的朋友可以參考下
    2020-09-09
  • Spring4如何自定義@Value功能詳解

    Spring4如何自定義@Value功能詳解

    這篇文章主要給大家介紹了關(guān)于Spring4如何自定義@Value功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring4具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-09-09
  • 解決java執(zhí)行cmd命令調(diào)用ffmpeg報(bào)錯(cuò)Concat error - No such filter ''[0,0]''問(wèn)題

    解決java執(zhí)行cmd命令調(diào)用ffmpeg報(bào)錯(cuò)Concat error - No such filter ''[0,0]

    這篇文章主要介紹了java執(zhí)行cmd命令,調(diào)用ffmpeg報(bào)錯(cuò)Concat error - No such filter '[0,0]'解決方法,本文通過(guò)截圖實(shí)例代碼說(shuō)明給大家介紹的非常詳細(xì),對(duì)大家的工作或?qū)W習(xí)有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • 基于Java實(shí)現(xiàn)無(wú)向環(huán)和有向環(huán)的檢測(cè)

    基于Java實(shí)現(xiàn)無(wú)向環(huán)和有向環(huán)的檢測(cè)

    這篇文章主要介紹了如何在?Java?中實(shí)現(xiàn)無(wú)向環(huán)和有向環(huán)的檢測(cè),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定的幫助,需要的可以參考一下
    2022-04-04
  • Springboot使用Rabbitmq的延時(shí)隊(duì)列+死信隊(duì)列實(shí)現(xiàn)消息延期消費(fèi)

    Springboot使用Rabbitmq的延時(shí)隊(duì)列+死信隊(duì)列實(shí)現(xiàn)消息延期消費(fèi)

    本文介紹了RabbitMQ的延時(shí)隊(duì)列和死信隊(duì)列,解釋了它們的工作原理及其應(yīng)用場(chǎng)景,延時(shí)隊(duì)列允許消息在設(shè)定的時(shí)間后被消費(fèi),結(jié)合實(shí)際案例,展示了如何實(shí)現(xiàn)和使用延時(shí)隊(duì)列和死信隊(duì)列,感興趣的朋友一起看看吧
    2025-01-01
  • Java獲取音頻文件的持續(xù)時(shí)間的實(shí)現(xiàn)方案

    Java獲取音頻文件的持續(xù)時(shí)間的實(shí)現(xiàn)方案

    在音視頻處理開(kāi)發(fā)中,FFmpeg 一直是最常用的跨平臺(tái)音視頻處理工具,然而在很多實(shí)際場(chǎng)景中,FFmpeg 并不總是理想的選擇,本文將展示一種完全不依賴(lài) FFprobe / FFmpeg 的方案,使用 Java 自帶的 javax.sound.sampled API 來(lái)獲取音頻文件的持續(xù)時(shí)間,需要的朋友可以參考下
    2025-11-11
  • Hibernate中Session增刪改查操作代碼詳解

    Hibernate中Session增刪改查操作代碼詳解

    這篇文章主要介紹了Hibernate中Session增刪改查操作代碼詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • SpringMVC統(tǒng)一異常處理實(shí)例代碼

    SpringMVC統(tǒng)一異常處理實(shí)例代碼

    這篇文章主要介紹了SpringMVC統(tǒng)一異常處理實(shí)例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11

最新評(píng)論

饶阳县| 延安市| 福贡县| 连江县| 桑日县| 印江| 栾城县| 杭锦后旗| 天门市| 安龙县| 甘孜| 玉屏| 叙永县| 云林县| 新竹县| 合水县| 阆中市| 明溪县| 西乡县| 镇远县| 休宁县| 宜兰市| 石门县| 五大连池市| 连云港市| 达拉特旗| 遵义市| 黄骅市| 罗田县| 延川县| 万荣县| 长汀县| 乐山市| 长宁区| 平原县| 宣武区| 宜阳县| 永济市| 呼玛县| 隆回县| 关岭|