<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          坑已踩好,MyBatis 幾種批量插入性能比較

          共 8916字,需瀏覽 18分鐘

           ·

          2021-09-23 18:00

          點擊上方藍色字體,選擇“設為星標”


          回復”學習資料“獲取學習寶典


          批處理數(shù)據(jù)主要有三種方式:

          1. 反復執(zhí)行單條插入語句

          2. foreach 拼接 sql

          3. 批處理

          一、前期準備

          基于Spring Boot + Mysql,同時為了省略get/set,使用了lombok,詳見pom.xml。

          1.1 表結構

          id 使用數(shù)據(jù)庫自增。

          DROP TABLE IF EXISTS `user_info_batch`;CREATE TABLE `user_info_batch` (`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵id',`user_name` varchar(100NOT NULL COMMENT '賬戶名稱',`pass_word` varchar(100) NOT NULL COMMENT '登錄密碼',`nick_name` varchar(30) NOT NULL COMMENT '昵稱',`mobile` varchar(30) NOT NULL COMMENT '手機號',`email` varchar(100) DEFAULT NULL COMMENT '郵箱地址',`gmt_create` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',`gmt_update` timestamp NULL DEFAULT NULL COMMENT '更新時間',PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT 'Mybatis Batch';

          1.2 項目配置文件

          細心的你可能已經發(fā)現(xiàn),數(shù)據(jù)庫url 后面跟了一段 rewriteBatchedStatements=true,有什么用呢?先不急,后面會介紹。

          # 數(shù)據(jù)庫配置spring:  datasource:    url: jdbc:mysql://47.111.118.152:3306/mybatis?rewriteBatchedStatements=true    username: mybatis    password: password    driver-class-name: com.mysql.cj.jdbc.Driver# mybatismybatis:  mapper-locations: classpath:mapper/*.xml  type-aliases-package: cn.van.mybatis.batch.entity

          1.3 實體類

          @Data@Accessors(chain = true)public class UserInfoBatchDO implements Serializable {    private Long id;
          private String userName;
          private String passWord;
          private String nickName;
          private String mobile;
          private String email;
          private LocalDateTime gmtCreate;
          private LocalDateTime gmtUpdate;}

          1.4 UserInfoBatchMapper

          public interface UserInfoBatchMapper {
          /** 單條插入 * @param info * @return */ int insert(UserInfoBatchDO info);
          /** * foreach 插入 * @param list * @return */ int batchInsert(List<UserInfoBatchDO> list);}

          1.5 UserInfoBatchMapper.xml

          <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="cn.van.mybatis.batch.mapper.UserInfoBatchMapper">
          <insert id="insert" parameterType="cn.van.mybatis.batch.entity.UserInfoBatchDO"> insert into user_info_batch (user_name, pass_word, nick_name, mobile, email, gmt_create, gmt_update) values (#{userName,jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR},#{nickName,jdbcType=VARCHAR}, #{mobile,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{gmtCreate,jdbcType=TIMESTAMP}, #{gmtUpdate,jdbcType=TIMESTAMP}) </insert>
          <insert id="batchInsert"> insert into user_info_batch (user_name, pass_word, nick_name, mobile, email, gmt_create, gmt_update) values <foreach collection="list" item="item" separator=","> (#{item.userName,jdbcType=VARCHAR}, #{item.passWord,jdbcType=VARCHAR}, #{item.nickName,jdbcType=VARCHAR}, #{item.mobile,jdbcType=VARCHAR}, #{item.email,jdbcType=VARCHAR}, #{item.gmtCreate,jdbcType=TIMESTAMP}, #{item.gmtUpdate,jdbcType=TIMESTAMP}) </foreach> </insert></mapper>

          1.6 預備數(shù)據(jù)

          為了方便測試,抽離了幾個變量,并進行提前加載。

             

           

          private List<UserInfoBatchDO> list = new ArrayList<>();    private List<UserInfoBatchDO> lessList = new ArrayList<>();    private List<UserInfoBatchDO> lageList = new ArrayList<>();    private List<UserInfoBatchDO> warmList = new ArrayList<>();    // 計數(shù)工具    private StopWatch sw = new StopWatch();

          為了方便組裝數(shù)據(jù),抽出了一個公共方法。

              

          private List<UserInfoBatchDO> assemblyData(int count){        List<UserInfoBatchDO> list = new ArrayList<>();        UserInfoBatchDO userInfoDO;        for (int i = 0;i < count;i++){            userInfoDO = new UserInfoBatchDO()                    .setUserName("Van")                    .setNickName("風塵博客")                    .setMobile("17098705205")                    .setPassWord("password")                    .setGmtUpdate(LocalDateTime.now());            list.add(userInfoDO);        }        return list;    }

          • 預熱數(shù)據(jù)

              
          @Beforepublic void assemblyData() {   list = assemblyData(200000);   lessList = assemblyData(2000);   lageList = assemblyData(1000000);   warmList = assemblyData(5);}

          二、反復執(zhí)行單條插入語句

          可能‘懶’的程序員會這么做,很簡單,直接在原先單條insert語句上嵌套一個for循環(huán)。

          2.1 對應 mapper 接口

          int insert(UserInfoBatchDO info);

          2.2 測試方法

          因為這種方法太慢,所以數(shù)據(jù)降低到 2000 條

          @Testpublic void insert() {    log.info("【程序熱身】");    for (UserInfoBatchDO userInfoBatchDO : warmList) {        userInfoBatchMapper.insert(userInfoBatchDO);    }    log.info("【熱身結束】");    sw.start("反復執(zhí)行單條插入語句");    // 這里插入 20w 條太慢了,所以我只插入了 2000 條    for (UserInfoBatchDO userInfoBatchDO : lessList) {        userInfoBatchMapper.insert(userInfoBatchDO);    }    sw.stop();    log.info("all cost info:{}",sw.prettyPrint());}

          2.3 執(zhí)行時間

          • 第一次

          -----------------------------------------ms     %     Task name-----------------------------------------59887  100%  反復執(zhí)行單條插入語句

          • 第二次

          -----------------------------------------ms     %     Task name-----------------------------------------64853  100%  反復執(zhí)行單條插入語句
          • 第三次

          -----------------------------------------ms     %     Task name-----------------------------------------58235  100%  反復執(zhí)行單條插入語句

          該方式插入2000 條數(shù)據(jù),執(zhí)行三次的平均時間:60991 ms。

          三、foreach 拼接SQL

          3.1 對應mapper 接口

          int batchInsert(List<UserInfoBatchDO> list);

          3.2 測試方法

          該方式和下一種方式都采用20w條數(shù)據(jù)測試。

          @Testpublic void batchInsert() {    log.info("【程序熱身】");    for (UserInfoBatchDO userInfoBatchDO : warmList) {        userInfoBatchMapper.insert(userInfoBatchDO);    }    log.info("【熱身結束】");    sw.start("foreach 拼接 sql");    userInfoBatchMapper.batchInsert(list);    sw.stop();    log.info("all cost info:{}",sw.prettyPrint());}

          3.3 執(zhí)行時間

          • 第一次

          -----------------------------------------ms     %     Task name-----------------------------------------18835  100%  foreach 拼接 sql

          • 第二次

          -----------------------------------------ms     %     Task name-----------------------------------------17895  100%  foreach 拼接 sql

          • 第三次

          -----------------------------------------ms     %     Task name-----------------------------------------19827  100%  foreach 拼接 sql

          該方式插入20w 條數(shù)據(jù),執(zhí)行三次的平均時間:18852 ms。

          四、批處理

          該方式 mapper 和xml 復用了 2.1

          4.1 rewriteBatchedStatements 參數(shù)

          我在測試一開始,發(fā)現(xiàn)改成 Mybatis Batch提交的方法都不起作用,實際上在插入的時候仍然是一條條記錄的插,而且速度遠不如原來 foreach 拼接SQL的方法,這是非常不科學的。

          后來才發(fā)現(xiàn)要批量執(zhí)行的話,連接URL字符串中需要新增一個參數(shù):rewriteBatchedStatements=true

          • rewriteBatchedStatements參數(shù)介紹

          MySql的JDBC連接的url中要加rewriteBatchedStatements參數(shù),并保證5.1.13以上版本的驅動,才能實現(xiàn)高性能的批量插入。MySql JDBC驅動在默認情況下會無視executeBatch()語句,把我們期望批量執(zhí)行的一組sql語句拆散,一條一條地發(fā)給MySql數(shù)據(jù)庫,批量插入實際上是單條插入,直接造成較低的性能。只有把rewriteBatchedStatements參數(shù)置為true, 驅動才會幫你批量執(zhí)行SQL。這個選項對INSERT/UPDATE/DELETE都有效。

          4.2 批處理準備

          • 手動注入 SqlSessionFactory 

              
          @Resourceprivate SqlSessionFactory sqlSessionFactory;
          • 測試代碼

          @Testpublic void processInsert() {    log.info("【程序熱身】");    for (UserInfoBatchDO userInfoBatchDO : warmList) {        userInfoBatchMapper.insert(userInfoBatchDO);    }    log.info("【熱身結束】");    sw.start("批處理執(zhí)行 插入");    // 打開批處理    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);    UserInfoBatchMapper mapper = session.getMapper(UserInfoBatchMapper.class);    for (int i = 0,length = list.size(); i < length; i++) {        mapper.insert(list.get(i));        //每20000條提交一次防止內存溢出        if(i%20000==19999){            session.commit();            session.clearCache();        }    }    session.commit();    session.clearCache();    sw.stop();    log.info("all cost info:{}",sw.prettyPrint());}

          4.3 執(zhí)行時間

          • 第一次

          -----------------------------------------ms     %     Task name-----------------------------------------09346  100%  批處理執(zhí)行 插入
          • 第二次

          -----------------------------------------ms     %     Task name-----------------------------------------08890  100%  批處理執(zhí)行 插入

          • 第三次

          -----------------------------------------ms     %     Task name-----------------------------------------09042  100%  批處理執(zhí)行 插入

          該方式插入20w 條數(shù)據(jù),執(zhí)行三次的平均時間:9092 ms。

          4.4 如果數(shù)據(jù)更大

          當我把數(shù)據(jù)擴大到 100w 時,foreach 拼接 sql 的方式已經無法完成插入了,所以我只能測試批處理的插入時間。

          測試時,僅需將 【4.2】測試代碼中的 list 切成 lageList 測試即可。

          • 第一次

          -----------------------------------------ms     %     Task name-----------------------------------------32419  100%  批處理執(zhí)行 插入
          • 第二次

          -----------------------------------------ms     %     Task name-----------------------------------------31935  100%  批處理執(zhí)行 插入
          • 第三次

          -----------------------------------------ms     %     Task name-----------------------------------------33048  100%  批處理執(zhí)行 插入

          該方式插入100w 條數(shù)據(jù),執(zhí)行三次的平均時間:32467 ms。

          五、總結

          批量插入方式數(shù)據(jù)量執(zhí)行三次的平均時間
          循環(huán)插入單條數(shù)據(jù)200060991 ms
          foreach 拼接sql20w18852 ms
          批處理20w9092 ms
          批處理100w32467 ms
          1. 循環(huán)插入單條數(shù)據(jù)雖然效率極低,但是代碼量極少,數(shù)據(jù)量較小時可以使用,但是數(shù)據(jù)量較大禁止使用,效率太低了;

          2. foreach 拼接sql的方式,使用時有大段的xml和sql語句要寫,很容易出錯,雖然效率尚可,但是真正應對大量數(shù)據(jù)的時候,依舊無法使用,所以不推薦使用;

          3. 批處理執(zhí)行是有大數(shù)據(jù)量插入時推薦的做法,使用起來也比較方便。


          作者:VanFan
          鏈接:https://juejin.cn/post/7007608714093920286

          來源:掘金

          后臺回復 學習資料 領取學習視頻


          如有收獲,點個在看,誠摯感謝

          瀏覽 121
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草青欲乐 | 中文精品一区 | 日韩毛片在线观看 | 美女扒开嫩嫩的尿口让人桶 | 亚洲天堂网2024 |