在項目開發中,我們經常需要進行大量數據的批量插入操作。然而,在實際應用中,插入大量數據時性能常常成為一個瓶頸。在我最近的項目中,我發現了一些能夠顯著提升批量插入性能的方法,并進行了一系列實驗來驗證它們的有效性。
今日內容介紹,大約花費15分鐘
圖片
我們使用了 mybatis-plus 框架,并采用其中的 saveBatch 方法進行批量數據插入。然而,通過深入研究源碼,我發現這個方法并沒有如我期望的那樣高效
圖片
這是因為最終在執行的時候還是通過for循環一條條執行insert,然后再一批的進行flush ,默認批的消息為1000
圖片
為了找到更優秀的解決方案,我展開了一場性能優化的探索之旅。好了我們現在開始探索
create table springboot_mp.tb_student( id bigint auto_increment comment '主鍵ID' primary key, stuid varchar(40) not null comment '學號', name varchar(30) null comment '姓名', age tinyint null comment '年齡', sex tinyint(1) null comment '性別 0 男 1 女', dept varchar(2000) null comment '院系', address varchar(400) null comment '家庭地址', constraint stuid unique (stuid));
圖片
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency></dependencies>
server: port: 8890spring: application: name: mybatis-demo #指定服務名 datasource: username: root password: root# url: jdbc:mysql://localhost:3306/springboot_mp?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true url: jdbc:mysql://localhost:3306/springboot_mp?useUnicode=true&characterEncoding=utf8 driver-class-name: com.mysql.cj.jdbc.Driver#mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl mapper-locations: classpath*:/mapper/**/*.xml
圖片
圖片
圖片
每次都是插入100000條數據
注意:因為我的電腦性能比較好,所以才插入這么多數據,大家可以插入1000進行實驗對比
首先,我采用了傳統的單條循環插入方法,將每條數據逐一插入數據庫,作為性能對比的基準。
/** * @author springboot葵花寶典 * @description: TODO */@SpringBootTestpublic class MybatisTest { @Autowired private StudentService studentService; @Autowired private SqlSessionFactory sqlSessionFactory; @Test public void MybatisBatchSaveOneByOne(){ SqlSession sqlSession = sqlSessionFactory.openSession(); try { StopWatch stopWatch = new StopWatch(); stopWatch.start("mybatis plus save one"); for (int i = 0; i < 100000; i++) { Student student = new Student(); student.setStuid("6840"+i); student.setName("zhangsan"+i); student.setAge((i%100)); if(i%2==0){ student.setSex(0); }else { student.setSex(1); } student.setDept("計算機學院"); student.setAddress("廣東省廣州市番禺"+i+"號"); //一條一條插入 studentService.save(student); } sqlSession.commit(); stopWatch.stop(); System.out.println("mybatis plus save one:" + stopWatch.getTotalTimeMillis()); } finally { sqlSession.close(); } }}
發現花費了195569毫秒
圖片
現在嘗試 mybatis-plus 提供的 saveBatch 方法,期望它能夠提高性能。
@Test public void MybatissaveBatch(){ SqlSession sqlSession = sqlSessionFactory.openSession(); try { List<Student> students = new ArrayList<>(); StopWatch stopWatch = new StopWatch(); stopWatch.start("mybatis plus save batch"); for (int i = 0; i < 100000; i++) { Student student = new Student(); student.setStuid("6840"+i); student.setName("zhangsan"+i); student.setAge((i%100)); if(i%2==0){ student.setSex(0); }else { student.setSex(1); } student.setDept("計算機學院"); student.setAddress("廣東省廣州市番禺"+i+"號"); //一條一條插入 students.add(student); } studentService.saveBatch(students); sqlSession.commit(); stopWatch.stop(); System.out.println("mybatis plus save batch:" + stopWatch.getTotalTimeMillis()); } finally { sqlSession.close(); } }
發現花費9204毫秒,比一條條插入數據性能提高十幾倍
3.手動拼接 SQL:挑戰傳統的方式
<insert id="saveBatch2"> insert into springboot_mp.tb_student ( stuid, name, age, sex, dept, address) values <foreach collection="students" item="stu" index="index" separator=","> ( #{stu.stuid}, #{stu.name}, #{stu.age}, #{stu.sex}, #{stu.dept}, #{stu.address}) </foreach></insert>
發現花費10958毫秒,比一條條插入數據性能提高十幾倍,但是和saveBatch性能相差不大
既然都驗證都這了,我就在想,要不要使用JDBC批量插入進行驗證一下,看會不會出現原始的才是最好的結果
4.JDBC 的 executeBatch 方法
嘗試直接使用 JDBC 提供的 executeBatch 方法,看是否有意外的性能提升。
@Test public void JDBCSaveBatch() throws SQLException { SqlSession sqlSession = sqlSessionFactory.openSession(); Connection connection = sqlSession.getConnection(); connection.setAutoCommit(false); String sql ="insert into springboot_mp.tb_student ( stuid, name, age, sex, dept, address) values (?, ?, ?, ?, ?, ?);"; try (PreparedStatement statement = connection.prepareStatement(sql)) { List<Student> students = new ArrayList<>(); StopWatch stopWatch = new StopWatch(); stopWatch.start("mybatis plus JDBCSaveBatch"); for (int i = 0; i < 100000; i++) { statement.setString(1,"6840"+i); statement.setString(2,"zhangsan"+i); statement.setInt(3,(i%100)); if(i%2==0){ statement.setInt(4,0); }else { statement.setInt(4,1); } statement.setString(5,"計算機學院"); statement.setString(6,"廣東省廣州市番禺"+i+"號"); statement.addBatch(); } statement.executeBatch(); connection.commit(); stopWatch.stop(); System.out.println("mybatis plus JDBCSaveBatch:" + stopWatch.getTotalTimeMillis()); } catch (Exception e){ System.out.println(e.getMessage()); } finally { sqlSession.close(); }
JDBC executeBatch 的性能會好點,耗費6667毫秒
但是感覺到這里以后,覺得時候還是比較長,有沒有可以再進行優化的方式,然后我就在ClientPreparedStatement類中發現有一個叫做rewriteBatchedStatements 的屬性,從名字來看是要重寫批操作的 Statement,前面batchHasPlainStatements 已經是 false,取反肯定是 true,所以只要這參數是 true 就會進行一波操作。rewriteBatchedStatements默認是 false。
圖片
圖片
大家也可以自行網上搜索一下這個神奇的屬性然后我在url添加上這個屬性
圖片
然后繼續跑了下 mybatis-plus 自帶的 saveBatch,果然性能大大提高直接由原來的9204毫秒,提升到現在的3903毫秒
圖片
再來跑一下JDBC 的 executeBatch ,果然也提高了。
直接由原來的6667毫秒,提升到了3794毫秒
批量保存方式 | 數據量(條) | 耗時(ms) |
單條循環插入 | 100000 | 195569 |
mybatis-plus saveBatch | 100000 | 9204 |
mybatis-plus saveBatch(添加 rewrite 參數) | 100000 | 3903 |
手動拼接 SQL | 100000 | 6667 |
JDBC executeBatch | 100000 | 10958 |
JDBC executeBatch(添加 rewrite 參數) | 100000 | 3794 |
通過實驗結果,我們可以得出以下結論:
如果您在項目中需要進行批量插入操作,我建議考慮以下優化方案:
本文鏈接:http://www.www897cc.com/showinfo-26-55101-0.htmlMyBatis批量插入數據優化,那叫一個優雅!
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: Vue 新一代開發者工具正式開源!
下一篇: Go語言中的性能考慮和優化