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

MyBatis批量插入優(yōu)化的方法步驟

 更新時(shí)間:2026年02月11日 08:21:29   作者:花寶寶hua  
本文主要介紹了MyBatis批量插入優(yōu)化的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

上周接了個(gè)數(shù)據(jù)遷移的活,要把10萬條數(shù)據(jù)從老系統(tǒng)導(dǎo)入新系統(tǒng)。

寫了個(gè)簡單的批量插入,跑起來一看——5分鐘。

領(lǐng)導(dǎo)說太慢了,能不能快點(diǎn)?

折騰了一下午,最后優(yōu)化到3秒,記錄一下過程。

最初的代碼(5分鐘)

最開始寫的很簡單,foreach循環(huán)插入:

// 方式1:循環(huán)單條插入(最慢)
for (User user : userList) {
    userMapper.insert(user);
}

10萬條數(shù)據(jù),每條都要走一次網(wǎng)絡(luò)請(qǐng)求、一次SQL解析、一次事務(wù)提交。

算一下:假設(shè)每條插入需要3ms,10萬條就是300秒 = 5分鐘。

這是最蠢的寫法,但我見過很多項(xiàng)目都這么寫。

第一次優(yōu)化:批量SQL(30秒)

把循環(huán)插入改成批量SQL:

<!-- Mapper.xml -->
<insert id="batchInsert">
    INSERT INTO user (name, age, email) VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.name}, #{item.age}, #{item.email})
    </foreach>
</insert>
// 分批插入,每批1000條
int batchSize = 1000;
for (int i = 0; i < userList.size(); i += batchSize) {
    int end = Math.min(i + batchSize, userList.size());
    List<User> batch = userList.subList(i, end);
    userMapper.batchInsert(batch);
}

從5分鐘降到30秒,提升10倍。

原理:一條SQL插入多條數(shù)據(jù),減少網(wǎng)絡(luò)往返次數(shù)。

但還有問題:30秒還是太慢。

第二次優(yōu)化:JDBC批處理(8秒)

MySQL有個(gè)參數(shù)叫rewriteBatchedStatements,開啟后可以把多條INSERT合并成一條。

第一步:修改數(shù)據(jù)庫連接URL

jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true

第二步:使用MyBatis的批處理模式

@Autowired
private SqlSessionFactory sqlSessionFactory;

public void batchInsertWithExecutor(List<User> userList) {
    try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        int batchSize = 1000;
        for (int i = 0; i < userList.size(); i++) {
            mapper.insert(userList.get(i));
            
            if ((i + 1) % batchSize == 0) {
                sqlSession.flushStatements();
                sqlSession.clearCache();
            }
        }
        sqlSession.flushStatements();
        sqlSession.commit();
    }
}

從30秒降到8秒。

原理ExecutorType.BATCH模式下,MyBatis會(huì)緩存SQL,最后一次性發(fā)送給數(shù)據(jù)庫執(zhí)行。配合rewriteBatchedStatements=true,MySQL驅(qū)動(dòng)會(huì)把多條INSERT合并。

第三次優(yōu)化:多線程并行(3秒)

8秒還是不夠快,上多線程:

public void parallelBatchInsert(List<User> userList) {
    int threadCount = 4;  // 根據(jù)數(shù)據(jù)庫連接池大小調(diào)整
    int batchSize = userList.size() / threadCount;
    
    ExecutorService executor = Executors.newFixedThreadPool(threadCount);
    List<Future<?>> futures = new ArrayList<>();
    
    for (int i = 0; i < threadCount; i++) {
        int start = i * batchSize;
        int end = (i == threadCount - 1) ? userList.size() : (i + 1) * batchSize;
        List<User> subList = userList.subList(start, end);
        
        futures.add(executor.submit(() -> {
            batchInsertWithExecutor(subList);
        }));
    }
    
    // 等待所有任務(wù)完成
    for (Future<?> future : futures) {
        try {
            future.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    executor.shutdown();
}

從8秒降到3秒。

注意事項(xiàng)

  1. 線程數(shù)不要超過數(shù)據(jù)庫連接池大小
  2. 如果需要事務(wù)一致性,這個(gè)方案不適用
  3. 要考慮主鍵沖突的問題

優(yōu)化效果對(duì)比

方案耗時(shí)提升倍數(shù)
循環(huán)單條插入300秒基準(zhǔn)
批量SQL30秒10倍
JDBC批處理8秒37倍
多線程并行3秒100倍

踩過的坑

坑1:foreach拼接SQL過長

<foreach collection="list" item="item" separator=",">

如果一次插入太多條,SQL會(huì)非常長,可能超過max_allowed_packet限制。

解決:分批插入,每批500-1000條。

坑2:rewriteBatchedStatements不生效

檢查幾個(gè)點(diǎn):

  1. URL參數(shù)是否正確:rewriteBatchedStatements=true
  2. 是否使用了ExecutorType.BATCH
  3. MySQL驅(qū)動(dòng)版本是否太舊

坑3:自增主鍵返回問題

批量插入時(shí)想獲取自增主鍵:

<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">

注意rewriteBatchedStatements=true時(shí),自增主鍵返回可能有問題,需要升級(jí)MySQL驅(qū)動(dòng)到8.0.17+。

坑4:內(nèi)存溢出

10萬條數(shù)據(jù)一次性加載到內(nèi)存,可能OOM。

解決:分頁讀取 + 分批插入。

int pageSize = 10000;
int total = countTotal();
for (int i = 0; i < total; i += pageSize) {
    List<User> page = selectByPage(i, pageSize);
    batchInsertWithExecutor(page);
}

最終方案代碼

@Service
public class BatchInsertService {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    /**
     * 高性能批量插入
     * 10萬條數(shù)據(jù)約3秒
     */
    public void highPerformanceBatchInsert(List<User> userList) {
        if (userList == null || userList.isEmpty()) {
            return;
        }
        
        int threadCount = Math.min(4, Runtime.getRuntime().availableProcessors());
        int batchSize = (int) Math.ceil((double) userList.size() / threadCount);
        
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        for (int i = 0; i < threadCount; i++) {
            int start = i * batchSize;
            int end = Math.min((i + 1) * batchSize, userList.size());
            
            if (start >= userList.size()) {
                latch.countDown();
                continue;
            }
            
            List<User> subList = new ArrayList<>(userList.subList(start, end));
            
            executor.submit(() -> {
                try {
                    doBatchInsert(subList);
                } finally {
                    latch.countDown();
                }
            });
        }
        
        try {
            latch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        executor.shutdown();
    }
    
    private void doBatchInsert(List<User> userList) {
        try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            
            for (int i = 0; i < userList.size(); i++) {
                mapper.insert(userList.get(i));
                
                if ((i + 1) % 1000 == 0) {
                    sqlSession.flushStatements();
                    sqlSession.clearCache();
                }
            }
            
            sqlSession.flushStatements();
            sqlSession.commit();
        }
    }
}

總結(jié)

優(yōu)化點(diǎn)關(guān)鍵配置
批量SQLforeach拼接,分批1000條
JDBC批處理rewriteBatchedStatements=true + ExecutorType.BATCH
多線程線程數(shù) ≤ 連接池大小

核心原則:減少網(wǎng)絡(luò)往返 + 減少事務(wù)次數(shù) + 并行處理。

到此這篇關(guān)于MyBatis批量插入優(yōu)化的方法步驟的文章就介紹到這了,更多相關(guān)MyBatis批量插入優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何基于SpringMVC實(shí)現(xiàn)斷點(diǎn)續(xù)傳(HTTP)

    如何基于SpringMVC實(shí)現(xiàn)斷點(diǎn)續(xù)傳(HTTP)

    這篇文章主要介紹了如何基于SpringMVC實(shí)現(xiàn)斷點(diǎn)續(xù)傳(HTTP),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • SpringSecurity微服務(wù)實(shí)戰(zhàn)之公共模塊詳解

    SpringSecurity微服務(wù)實(shí)戰(zhàn)之公共模塊詳解

    這篇文章主要為大家介紹了SpringSecurity微服務(wù)實(shí)戰(zhàn)之公共模塊詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 一文掌握Spring?Boot?日志文件

    一文掌握Spring?Boot?日志文件

    日志是程序的重要組成部分,日志對(duì)于我們來說,最主要的用途就是排除和定位問題,這篇文章主要介紹了Spring?Boot?日志文件,需要的朋友可以參考下
    2023-03-03
  • jdk環(huán)境配置Maven環(huán)境配置實(shí)踐

    jdk環(huán)境配置Maven環(huán)境配置實(shí)踐

    文章介紹了如何配置JDK和Maven環(huán)境變量,并通過命令驗(yàn)證配置是否成功,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2026-02-02
  • 淺談JVM系列之從匯編角度分析NullCheck

    淺談JVM系列之從匯編角度分析NullCheck

    在virtual call中執(zhí)行nullcheck的時(shí)候,如果已經(jīng)知道傳遞的參數(shù)是非空的。JIT會(huì)對(duì)代碼進(jìn)行優(yōu)化嗎?本文將詳細(xì)介紹JVM系列之從匯編角度分析NullCheck。
    2021-06-06
  • 詳解安裝Eclipse Maven插件的幾種方法

    詳解安裝Eclipse Maven插件的幾種方法

    本篇文章主要介紹了詳解安裝Eclipse Maven插件的幾種方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-01-01
  • SpringLDAP目錄服務(wù)之LdapTemplate與LDAP操作方式

    SpringLDAP目錄服務(wù)之LdapTemplate與LDAP操作方式

    本文將深入探討Spring LDAP的核心概念、LdapTemplate的使用方法以及如何執(zhí)行常見的LDAP操作,幫助開發(fā)者有效地將LDAP集成到Spring應(yīng)用中,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • spark中使用groupByKey進(jìn)行分組排序的示例代碼

    spark中使用groupByKey進(jìn)行分組排序的示例代碼

    這篇文章主要介紹了spark中使用groupByKey進(jìn)行分組排序的實(shí)例代碼,本文通過實(shí)例代碼給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • Java Eclipse中實(shí)現(xiàn)快速替換變量

    Java Eclipse中實(shí)現(xiàn)快速替換變量

    這篇文章主要介紹了Java Eclipse中實(shí)現(xiàn)快速替換變量,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java程序包不存在的兩種解決方法

    Java程序包不存在的兩種解決方法

    有時(shí)候我們?cè)趯?dǎo)入程序之后,系統(tǒng)會(huì)給出錯(cuò)誤提示:Java:程序包xxxx不存在,本文主要介紹了Java程序包不存在的兩種解決方法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-02-02

最新評(píng)論

五河县| 堆龙德庆县| 比如县| 德州市| 静安区| 西安市| 芒康县| 瑞丽市| 苍溪县| 通江县| 舞阳县| 内江市| 安溪县| 乌海市| 昭苏县| 阳新县| 肇东市| 柯坪县| 大理市| 华安县| 平罗县| 宾阳县| 江津市| 武清区| 黎川县| 同心县| 辽宁省| 汝城县| 宝清县| 汶川县| 东乡族自治县| 麻城市| 扬州市| 道真| 句容市| 阳东县| 赤峰市| 杨浦区| 肥东县| 福建省| 濮阳市|