日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
如何進(jìn)行批量SQL優(yōu)化

如何進(jìn)行批量SQL優(yōu)化,針對這個(gè)問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡單易行的方法。

成都創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站制作、做網(wǎng)站、赤城網(wǎng)絡(luò)推廣、小程序制作、赤城網(wǎng)絡(luò)營銷、赤城企業(yè)策劃、赤城品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供赤城建站搭建服務(wù),24小時(shí)服務(wù)熱線:13518219792,官方網(wǎng)址:www.cdcxhl.com

有時(shí)在工作中,我們需要將大量的數(shù)據(jù)持久化到數(shù)據(jù)庫中,如果數(shù)據(jù)量很大的話直接插入的執(zhí)行速度非常慢,并且由于插入操作也沒有太多能夠進(jìn)行sql優(yōu)化的地方,所以只能從程序代碼的角度進(jìn)行優(yōu)化。所以本文將嘗試使用幾種不同方式對插入操作進(jìn)行優(yōu)化,看看如何能夠最大程度的縮短SQL執(zhí)行時(shí)間。

以插入1000條數(shù)據(jù)為例,首先進(jìn)行數(shù)據(jù)準(zhǔn)備,用于插入數(shù)據(jù)庫測試:

private List prepareData(){
    List orderList=new ArrayList<>();
    for (int i = 1; i <= 1000; i++) {
        Order order=new Order();
        order.setId(Long.valueOf(i));
        order.setOrderNumber("A");
        order.setMoney(100D);
        order.setTenantId(1L);
        orderList.add(order);
    }
    return orderList;
}

直接插入

首先測試直接插入1000條數(shù)據(jù):

public void noBatch() {
    List orderList = prepareData();
    long startTime = System.currentTimeMillis();
    for (Order order : orderList) {
        orderMapper.insert(order);
    }
    System.out.println("總耗時(shí): " + (System.currentTimeMillis() - startTime) / 1000.0 + "s");
}

執(zhí)行時(shí)間如下:

如何進(jìn)行批量SQL優(yōu)化

mybatis-plus 批量插入

接下來,使用mybatis-plus的批量查詢,我們自己的Service接口需要繼承IService接口:

public interface SqlService extends IService {
}

在實(shí)現(xiàn)類SqlServiceImpl中直接調(diào)用saveBatch方法:

public void plusBatch() {
    List orderList = prepareData();
    long startTime = System.currentTimeMillis();
    saveBatch(orderList);
    System.out.println("總耗時(shí): " + (System.currentTimeMillis() - startTime) / 1000.0 + "s");
}

執(zhí)行代碼,查看運(yùn)行時(shí)間:

如何進(jìn)行批量SQL優(yōu)化

可以發(fā)現(xiàn),使用mybatis-plus的批量插入并沒有比循環(huán)單條插入顯著縮短時(shí)間,所以來查看一下saveBatch方法的源碼:

@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection entityList, int batchSize) {
    String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
    return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
}

其中調(diào)用了executeBatch方法:

protected  boolean executeBatch(Collection list, int batchSize, BiConsumer consumer) {
    Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
    return !CollectionUtils.isEmpty(list) && executeBatch(sqlSession -> {
        int size = list.size();
        int i = 1;
        for (E element : list) {
            consumer.accept(sqlSession, element);
            if ((i % batchSize == 0) || i == size) {
                sqlSession.flushStatements();
            }
            i++;
        }
    });
}

在for循環(huán)中,consumer的accept執(zhí)行的是sqlSession的insert操作,這一階段都是對sql的拼接,只有到最后當(dāng)for循環(huán)執(zhí)行完成后,才會(huì)將數(shù)據(jù)批量刷新到數(shù)據(jù)庫中。也就是說,之前我們向數(shù)據(jù)庫服務(wù)器發(fā)起了1000次請求,但是使用批量插入,只需要發(fā)起一次請求就可以了。如果拋出異常,則會(huì)進(jìn)行回滾,不會(huì)向數(shù)據(jù)庫中寫入數(shù)據(jù)。但是雖然減少了數(shù)據(jù)庫請求的次數(shù),對于縮短執(zhí)行時(shí)間并沒有顯著的提升。

并行流

Stream是JAVA8中用于處理集合的關(guān)鍵抽象概念,可以進(jìn)行復(fù)雜的查找、過濾、數(shù)據(jù)映射等操作。而并行流Parallel Stream,可以將整個(gè)數(shù)據(jù)內(nèi)容分成多個(gè)數(shù)據(jù)塊,并使用多個(gè)線程分別處理每個(gè)數(shù)據(jù)塊的流。在大量數(shù)據(jù)的插入操作中,不存在數(shù)據(jù)的依賴的耦合關(guān)系,因此可以進(jìn)行拆分使用并行流進(jìn)行插入。測試插入的代碼如下:

public void stream(){
    List orderList = prepareData();
    long startTime = System.currentTimeMillis();
    orderList.parallelStream().forEach(order->orderMapper.insert(order));
    System.out.println("總耗時(shí): " + (System.currentTimeMillis() - startTime) / 1000.0 + "s");
}

還是先對上面的代碼進(jìn)行測試:

如何進(jìn)行批量SQL優(yōu)化

可以發(fā)現(xiàn)速度比之前快了很多,這是因?yàn)椴⑿辛鞯讓邮褂昧薋ork/Join框架,具體來說使用了“分而治之”的思想,對任務(wù)進(jìn)行了拆分,使用不同線程進(jìn)行執(zhí)行,最后匯總(對Fork/Join不熟悉的同學(xué)可以回顧一下請求合并與分而治之這篇文章,里面介紹了它的基礎(chǔ)使用)。并行流在底層使用了ForkJoinPool線程池,從ForkJoinPool的默認(rèn)構(gòu)造函數(shù)中看出,它擁有的默認(rèn)線程數(shù)量等于計(jì)算機(jī)的邏輯處理器數(shù)量:

public ForkJoinPool() {
    this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
         defaultForkJoinWorkerThreadFactory, null, false);
}

也就是說,如果我們服務(wù)器是邏輯8核的話,那么就會(huì)有8個(gè)線程來同時(shí)執(zhí)行插入操作,大大縮短了執(zhí)行的時(shí)間。并且ForkJoinPool線程池為了提高任務(wù)的并行度和吞吐量,采用了任務(wù)竊取機(jī)制,能夠進(jìn)一步的縮短執(zhí)行的時(shí)間。

Fork/Join

在并行流中,創(chuàng)建的ForkJoinPool的線程數(shù)量是固定的,那么通過手動(dòng)修改線程池中線程的數(shù)量,能否進(jìn)一步的提高執(zhí)行效率呢?一般而言,在線程池中,設(shè)置線程數(shù)量等于處理器數(shù)量就可以了,因?yàn)槿绻麆?chuàng)建過多線程,線程頻繁切換上下文也會(huì)額外消耗時(shí)間,反而會(huì)增加執(zhí)行的總體時(shí)間。但是對于批量SQL的插入操作,沒有復(fù)雜的業(yè)務(wù)處理邏輯,僅僅是需要頻繁的與數(shù)據(jù)庫進(jìn)行交互,屬于I/O密集型操作。而對于I/O密集型操作,程序中存在大量I/O等待占據(jù)時(shí)間,導(dǎo)致CPU使用率較低。所以我們嘗試增加線程數(shù)量,來看一下能否進(jìn)一步縮短執(zhí)行時(shí)間呢?

定義插入任務(wù),因?yàn)椴恍枰祷?,直接繼承RecursiveAction父類。size是每個(gè)隊(duì)列中包含的任務(wù)數(shù)量,在構(gòu)造方法中傳入,如果一個(gè)隊(duì)列中的任務(wù)數(shù)量大于它那么就繼續(xù)進(jìn)行拆分,直到任務(wù)數(shù)量足夠?。?/p>

public class BatchInsertTask extends RecursiveAction {
    private List list;
    private BaseMapper mapper;
    private int size;

    public BatchInsertTask(List list, BaseMapper mapper, int size) {
        this.list = list;
        this.mapper = mapper;
        this.size = size;
    }

    @Override
    protected void compute() {
        if (list.size() <= size) {
            list.stream().forEach(item -> mapper.insert(item));
        } else {
            int middle = list.size() / 2;
            List left = list.subList(0, middle);
            List right = list.subList(middle, list.size());
            BatchInsertTask leftTask = new BatchInsertTask<>(left, mapper, size);
            BatchInsertTask rightTask = new BatchInsertTask<>(right, mapper, size);
            invokeAll(leftTask, rightTask);
        }
    }
}

使用ForkJoinPool運(yùn)行上面定義的任務(wù),線程池中的線程數(shù)取CPU線程的2倍,將執(zhí)行的SQL條數(shù)均分到每個(gè)線程的執(zhí)行隊(duì)列中:

public class BatchSqlUtil {
    public static  void runSave(List list, BaseMapper mapper) {
        int processors = getProcessors();
        ForkJoinPool forkJoinPool = new ForkJoinPool(processors);
        int size = (int) Math.ceil((double)list.size() / processors);
        BatchInsertTask task = new BatchInsertTask(list, mapper, size);
        forkJoinPool.invoke(task);
    }

    private static int getProcessors() {
        int processors = Runtime.getRuntime().availableProcessors();
        return processors<<=1;
    }
}

啟動(dòng)測試代碼:

public void batch() {
    List orderList = prepareData();
    long startTime = System.currentTimeMillis();
    BatchSqlUtil.runSave(orderList,orderMapper);
    System.out.println("總耗時(shí): " + (System.currentTimeMillis() - startTime) / 1000.0 + "s");
}

查看運(yùn)行時(shí)間:

如何進(jìn)行批量SQL優(yōu)化

可以看到,通過增加ForkJoinPool中的線程,可以進(jìn)一步的縮短批量插入的時(shí)間。

關(guān)于如何進(jìn)行批量SQL優(yōu)化問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。


分享名稱:如何進(jìn)行批量SQL優(yōu)化
本文地址:http://www.dlmjj.cn/article/josppp.html