<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>

          SpringBoot 高效批量插入萬(wàn)級(jí)數(shù)據(jù),哪種方式最強(qiáng)?

          共 15299字,需瀏覽 31分鐘

           ·

          2023-08-30 14:40

              

          上方藍(lán)色“ JavaPub ”,選擇“設(shè)為星標(biāo)”

          回復(fù)“ 資料 ”獲取整理好的 面試資料


          原文:
          blog.csdn.net/weixin_44030143/article/details/130825037



          準(zhǔn)備工作

          1、Maven項(xiàng)目中pom.xml文件引入的相關(guān)依賴如下

              <dependencies>
              <!-- SpringBoot Web模塊依賴 -->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>

              <!-- MyBatis-Plus 依賴 -->
              <dependency>
                  <groupId>com.baomidou</groupId>
                  <artifactId>mybatis-plus-boot-starter</artifactId>
                  <version>3.3.1</version>
              </dependency>

              <!-- 數(shù)據(jù)庫(kù)連接驅(qū)動(dòng) -->
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
              </dependency>

              <!-- 使用注解,簡(jiǎn)化代碼-->
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
              </dependency>
          </dependencies>

          2、application.yml配置屬性文件內(nèi)容(重點(diǎn):開啟批處理模式)

              server:
              端口號(hào) 
              port: 8080

          #  MySQL連接配置信息(以下僅簡(jiǎn)單配置,更多設(shè)置可自行查看)
          spring:
              datasource:
                   連接地址(解決UTF-8中文亂碼問(wèn)題 + 時(shí)區(qū)校正)
                          (rewriteBatchedStatements=true 開啟批處理模式)
                  url: jdbc:mysql://127.0.0.1:3306/bjpowernode?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
                   用戶名
                  username: root
                   密碼
                  password: xxx
                   連接驅(qū)動(dòng)名稱
                  driver-class-name: com.mysql.cj.jdbc.Driver

          3、Entity實(shí)體類(測(cè)試)

              /**
           *   Student 測(cè)試實(shí)體類
           *   
           *   @Data注解:引入Lombok依賴,可省略Setter、Getter方法
           */

          @Data
          @TableName(value = "student")
          public class Student {
              
              /**  主鍵  type:自增 */
              @TableId(type = IdType.AUTO)
              private int id;

              /**  名字 */
              private String name;

              /**  年齡 */
              private int age;

              /**  地址 */
              private String addr;

              /**  地址號(hào)  @TableField:與表字段映射 */
              @TableField(value = "addr_num")
              private String addrNum;

              public Student(String name, int age, String addr, String addrNum) {
                  this.name = name;
                  this.age = age;
                  this.addr = addr;
                  this.addrNum = addrNum;
              }
          }

          4、數(shù)據(jù)庫(kù)student表結(jié)構(gòu)(注意:無(wú)索引)

          259948da2db70a4c7249a53188c160d3.webp

          測(cè)試工作


          1、for循環(huán)插入(單條)(總耗時(shí):177秒)

          總結(jié):測(cè)試平均時(shí)間約是177秒,實(shí)在是不忍直視(捂臉),因?yàn)槔胒or循環(huán)進(jìn)行單條插入時(shí),每次都是在獲取連接(Connection)、釋放連接和資源關(guān)閉等操作上,(如果數(shù)據(jù)量大的情況下)極其消耗資源,導(dǎo)致時(shí)間長(zhǎng)。

              @GetMapping("/for")
          public void forSingle(){
              // 開始時(shí)間
              long startTime = System.currentTimeMillis();
              for (int i = 0; i < 50000; i++){
                  Student student = new Student("李毅" + i,24,"張家界市" + i,i + "號(hào)");
                  studentMapper.insert(student);
              }
              // 結(jié)束時(shí)間
              long endTime = System.currentTimeMillis();
              System.out.println("插入數(shù)據(jù)消耗時(shí)間:" + (endTime - startTime));
          }

          測(cè)試時(shí)間: e02c1022c703e43d73e622a109ba95a7.webp 2、拼接SQL語(yǔ)句(總耗時(shí):2.9秒)

          簡(jiǎn)明:拼接格式:insert into student(xxxx) value(xxxx),(xxxx),(xxxxx)…

          總結(jié):拼接結(jié)果就是將所有的數(shù)據(jù)集成在一條SQL語(yǔ)句的value值上,其由于提交到服務(wù)器上的insert語(yǔ)句少了,網(wǎng)絡(luò)負(fù)載少了,性能也就提上去。

          但是當(dāng)數(shù)據(jù)量上去后,可能會(huì)出現(xiàn)內(nèi)存溢出、解析SQL語(yǔ)句耗時(shí)等情況,但與第一點(diǎn)相比,提高了極大的性能。

               /**
           * 拼接sql形式
           */

          @GetMapping("/sql")
          public void sql(){
              ArrayList<Student> arrayList = new ArrayList<>();
              long startTime = System.currentTimeMillis();
              for (int i = 0; i < 50000; i++){
                  Student student = new Student("李毅" + i,24,"張家界市" + i,i + "號(hào)");
                  arrayList.add(student);
              }
              studentMapper.insertSplice(arrayList);
              long endTime = System.currentTimeMillis();
              System.out.println("插入數(shù)據(jù)消耗時(shí)間:" + (endTime - startTime));
          }

          mapper

              public interface StudentMapper extends BaseMapper<Student{

              @Insert("<script>" +
                      "insert into student (name, age, addr, addr_num) values " +
                      "<foreach collection='studentList' item='item' separator=','> " +
                      "(#{item.name}, #{item.age},#{item.addr}, #{item.addrNum}) " +
                      "</foreach> " +
                      "</script>")

              int insertSplice(@Param("studentList") List<Student> studentList);
          }

          測(cè)試結(jié)果 022be68a4b6a467ff1aa9d5f65c2e9a4.webp 3、批量插入saveBatch(總耗時(shí):2.7秒)

          簡(jiǎn)明:使用MyBatis-Plus實(shí)現(xiàn)IService接口中批處理saveBatch()方法,對(duì)底層源碼進(jìn)行查看時(shí),可發(fā)現(xiàn)其實(shí)是for循環(huán)插入,但是與第一點(diǎn)相比,為什么性能上提高了呢?

          因?yàn)槔梅制幚恚╞atchSize = 1000) + 分批提交事務(wù)的操作,從而提高性能,并非在Connection上消耗性能。(目前個(gè)人覺得較優(yōu)化方案)

              /**
           * mybatis-plus的批處理模式
           */

          @GetMapping("/saveBatch1")
          public void saveBatch1(){
              ArrayList<Student> arrayList = new ArrayList<>();
              long startTime = System.currentTimeMillis();
              // 模擬數(shù)據(jù)
              for (int i = 0; i < 50000; i++){
                  Student student = new Student("李毅" + i,24,"張家界市" + i,i + "號(hào)");
                  arrayList.add(student);
              }
              // 批量插入
              studentService.saveBatch(arrayList);
              long endTime = System.currentTimeMillis();
              System.out.println("插入數(shù)據(jù)消耗時(shí)間:" + (endTime - startTime));
          }

          22b97d646965fb6e589541fbf1b880d6.webp重點(diǎn)注意:MySQLJDBC驅(qū)動(dòng)默認(rèn)情況下忽略saveBatch()方法中的executeBatch()語(yǔ)句,將需要批量處理的一組SQL語(yǔ)句進(jìn)行拆散,執(zhí)行時(shí)一條一條給MySQL數(shù)據(jù)庫(kù),造成實(shí)際上是分片插入,即與單條插入方式相比,有提高,但是性能未能得到實(shí)質(zhì)性的提高。

          測(cè)試:數(shù)據(jù)庫(kù)連接URL地址缺少 rewriteBatchedStatements = true 參數(shù)情況

              #  MySQL連接配置信息
          spring:
              datasource:
                   連接地址(未開啟批處理模式)
                  url: jdbc:mysql://127.0.0.1:3306/bjpowernode?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
                   用戶名
                  username: root
                   密碼
                  password: xxx
                   連接驅(qū)動(dòng)名稱
                  driver-class-name: com.mysql.cj.jdbc.Driver

          測(cè)試結(jié)果:10541 約等于 10.5秒(未開啟批處理模式) 5c90cef8fc27db6ff0f4486e473c28e4.webp 4、循環(huán)插入 + 開啟批處理模式(總耗時(shí):1.7秒)(重點(diǎn):一次性提交)

          簡(jiǎn)明:開啟批處理,關(guān)閉自動(dòng)提交事務(wù),共用同一個(gè)SqlSession之后,for循環(huán)單條插入的性能得到實(shí)質(zhì)性的提高;由于同一個(gè)SqlSession省去對(duì)資源相關(guān)操作的耗能、減少對(duì)事務(wù)處理的時(shí)間等,從而極大程度上提高執(zhí)行效率。(目前個(gè)人覺得較優(yōu)化方案)

              /**
           * 共用同一個(gè)SqlSession
           */

          @GetMapping("/forSaveBatch")
          public void forSaveBatch(){
              //  開啟批量處理模式 BATCH 、關(guān)閉自動(dòng)提交事務(wù) false

              SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH,false);
              //  反射獲取,獲取Mapper
              StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
              long startTime = System.currentTimeMillis();
              for (int i = 0 ; i < 50000 ; i++){
                  Student student = new Student("李毅" + i,24,"張家界市" + i,i + "號(hào)");
                  studentMapper.insert(student);
              }
              // 一次性提交事務(wù)
              sqlSession.commit();
              // 關(guān)閉資源
              sqlSession.close();
              long endTime = System.currentTimeMillis();
              System.out.println("總耗時(shí): " + (endTime - startTime));
          }

          5、ThreadPoolTaskExecutor(總耗時(shí):1.7秒)

          ?

          目前個(gè)人覺得較優(yōu)化方案

          ?
              @Autowired
          private ThreadPoolTaskExecutor threadPoolTaskExecutor;
          @Autowired
          private PlatformTransactionManager transactionManager;

          @GetMapping("/batchInsert2")
          public void batchInsert2() {
              ArrayList<Student> arrayList = new ArrayList<>();
              long startTime = System.currentTimeMillis();
              // 模擬數(shù)據(jù)
              for (int i = 0; i < 50000; i++){
                  Student student = new Student("李毅" + i,24,"張家界市" + i,i + "號(hào)");
                  arrayList.add(student);
              }
              int count = arrayList.size();
              int pageSize = 1000// 每批次插入的數(shù)據(jù)量
              int threadNum = count / pageSize + 1// 線程數(shù)
              CountDownLatch countDownLatch = new CountDownLatch(threadNum);
              for (int i = 0; i < threadNum; i++) {
                  int startIndex = i * pageSize;
                  int endIndex = Math.min(count, (i + 1) * pageSize);
                  List<Student> subList = arrayList.subList(startIndex, endIndex);
                  threadPoolTaskExecutor.execute(() -> {
                      DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
                      TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
                      try {
                          studentMapper.insertSplice(subList);
                          transactionManager.commit(status);
                      } catch (Exception e) {
                          transactionManager.rollback(status);
                          throw e;
                      } finally {
                          countDownLatch.countDown();
                      }
                  });
              }
              try {
                  countDownLatch.await();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }

          首先定義了一個(gè)線程池(ThreadPoolTaskExecutor),用于管理線程的生命周期和執(zhí)行任務(wù)。然后,我們將要插入的數(shù)據(jù)列表按照指定的批次大小分割成多個(gè)子列表,并開啟多個(gè)線程來(lái)執(zhí)行插入操作。

          首先通過(guò) TransactionManager 獲取事務(wù)管理器,并使用 TransactionDefinition 定義事務(wù)屬性。然后,在每個(gè)線程中,我們通過(guò)  transactionManager.getTransaction()  方法獲取事務(wù)狀態(tài),并在插入操作中使用該狀態(tài)來(lái)管理事務(wù)。

          在插入操作完成后,我們?cè)俑鶕?jù)操作結(jié)果調(diào)用 transactionManager.commit() 或  transactionManager.rollback()  方法來(lái)提交或回滾事務(wù)。在每個(gè)線程執(zhí)行完畢后,都會(huì)調(diào)用 CountDownLatch 的 countDown() 方法,以便主線程等待所有線程都執(zhí)行完畢后再返回。

          < END > 我們創(chuàng)建了一個(gè)高質(zhì)量的技術(shù)交流群,與優(yōu)秀的人在一起,自己也會(huì)優(yōu)秀起來(lái),趕緊 點(diǎn)擊加群 ,享受一起成長(zhǎng)的快樂(lè)。另外,如果你最近想跳槽的話,年前我花了2周時(shí)間收集了一波大廠面經(jīng),節(jié)后準(zhǔn)備跳槽的可以 點(diǎn)擊這里領(lǐng)取

          推薦閱讀

          ··································

          你好,我是 JavaPub,多年開發(fā)老司機(jī),區(qū)塊鏈從業(yè)者、自媒體創(chuàng)作者、站長(zhǎng)。喜歡自由、開放。選擇計(jì)算機(jī)這個(gè)行業(yè),就是因?yàn)闊釔邸R宦愤^(guò)來(lái),給我最深的感受就是一定要不斷學(xué)習(xí)并關(guān)注前沿。只要你能堅(jiān)持下來(lái),多思考、少抱怨、勤動(dòng)手,就很容易實(shí)現(xiàn)彎道超車!所以,不要問(wèn)我現(xiàn)在干什么是否來(lái)得及。如果你看好一個(gè)事情,一定是堅(jiān)持了才能看到希望,而不是看到希望才去堅(jiān)持。相信我,只要堅(jiān)持下來(lái),你一定比現(xiàn)在更好!如果你還沒(méi)什么方向,可以先關(guān)注我,這里會(huì)經(jīng)常分享一些前沿資訊,幫你積累彎道超車的資本。

          瀏覽 81
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  嗯~公啊~嗯~挺进啊~A片视频 | 激情久久网| 天堂男人资源网 | 亚洲狼友 | 亚洲内射学生妹 |