Mybatis-Plus多種批量插入方案對比小結(jié)
背景
六月某日上線了一個日報表任務(wù),因是第一次上線,故需要為歷史所有日期都初始化一次報表數(shù)據(jù)
在執(zhí)行過程中發(fā)現(xiàn)新增特別的慢:插入十萬條左右的數(shù)據(jù),SQL執(zhí)行耗費高達三分多鐘
因很早就聽聞過mybatis-plus的[偽]批量新增的問題,很快鎖定問題并進行修復(fù),下面細節(jié)描述多種批量新增方案的具體性能表現(xiàn)
# 測試表結(jié)構(gòu) DROP TABLE IF EXISTS `biz_batch_insert_test`; CREATE TABLE `biz_batch_insert_test` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(600) DEFAULT NULL, `age` tinyint(4) unsigned DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; # name字段給的長度大了些,模擬生產(chǎn)實際表結(jié)構(gòu)占用 # 測試庫版本:5.7.5
方案一:傳統(tǒng)for循環(huán)
@Test
public void testUserInsert() {
long l = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
TestUser testUser = new TestUser();
testUser.setName("中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國");
testUser.setAge(20);
testUserService.save(testUser);
}
System.out.println("毫秒==>" + (System.currentTimeMillis() - l));
}
# 插入10萬條耗時:238040ms,大約4分鐘
方案二:使用Mybatis-Plus的saveBatch
@Test
public void testUserInsert() {
long l = System.currentTimeMillis();
List<TestUser> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
TestUser testUser = new TestUser();
testUser.setName("中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國");
testUser.setAge(20);
list.add(testUser);
}
testUserService.saveBatch(list);
System.out.println("毫秒==>" + (System.currentTimeMillis() - l));
}
# 插入耗時:62180ms,大約1分鐘
這里先留下一張saveBatch的SQL日志截圖,這里的日志是一個insert into語句,下面帶了一千條數(shù)據(jù)(MP默認(rèn)一千條一個批次),使用Mybatis Log插件查看還是單條的SQL

方案三:在方案二的基礎(chǔ)上修改MySQL連接參數(shù):rewriteBatchedStatements=true
# 與方案二測試代碼相同 # 插入耗時:35260ms,大約半分鐘
其SQL日志也如上方案二所示,MySQL Jdbc驅(qū)動在默認(rèn)情況下會無視executeBatch()語句,把我們期望批量執(zhí)行的一組sql語句拆散,一條一條地發(fā)給MySQL數(shù)據(jù)庫,直接造成較低的性能
Mysql連接配置鏈接

方案四:使用Mybatis-Plus提供的擴展插件:InsertBatchSomeColumn
@Test
public void testUserInsert() {
long l = System.currentTimeMillis();
List<TestUser> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
TestUser testUser = new TestUser();
testUser.setName("中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國中國");
testUser.setAge(20);
list.add(testUser);
// 因為mysql的參數(shù)max_allowed_packet限制,所以這里程序改成單批1000條
if(list.size() == 1000){
testUserMapper.insertBatchSomeColumn(list);
list.clear();
}
}
System.out.println("毫秒==>" + (System.currentTimeMillis() - l));
}
# 插入耗時:24410ms,大約24秒
再來看看此時控制臺的SQL,與方案二的SQL有著明顯的區(qū)別,這里是將批次插入的數(shù)據(jù)拼接在同一條SQL中,對于MySQL處理來說,這是真的批量新增

配置方式
// 1. 自定義SQL注入器
public class BatchSaveSqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
// 注意:保留mybatis-plus的自帶方法
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));
return methodList;
}
}
// 2. 實現(xiàn)自定義baseMapper
public interface BatchSaveBaseMapper<T> extends BaseMapper<T> {
/**
* 批量插入 僅適用于mysql
*
* @param entityList
* 實體列表
* @return 影響行數(shù)
*/
Integer insertBatchSomeColumn(Collection<T> entityList);
}
// 3. 注入插件
// 方式一
@Configuration
public class MybatisPlusConfig {
/**
* 分頁插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
/**
* SQL注入器
*/
@Bean
public BatchSaveSqlInjector easySqlInjector() {
return new BatchSaveSqlInjector();
}
}
// 方式二
// 若項目有自定義SqlSessionFactory,也可在初始化時將自定義SQL注入器植入,參考下圖MybatisPlusAutoConfiguration.sqlSessionFactory的做法

總結(jié)
| 插入10萬數(shù)據(jù)耗時(秒) | |
|---|---|
| mybatis-plus:save | 238 |
| mybatis-plus的saveBatch:rewriteBatchedStatements=false | 62 |
| mybatis-plus的saveBatch:rewriteBatchedStatements=true | 35 |
| mybatis-plus擴展插件:InsertBatchSomeColumn | 24 |
到此這篇關(guān)于Mybatis-Plus多種批量插入方案對比小結(jié)的文章就介紹到這了,更多相關(guān)Mybatis-Plus多種批量插入內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java線程池ExecutorService超時處理小結(jié)
使用ExecutorService時,設(shè)置子線程執(zhí)行超時是一個常見需求,本文就來詳細的介紹一下ExecutorService超時的三種方法,感興趣的可以了解一下2024-09-09
基于Transactional事務(wù)的使用以及注意說明
這篇文章主要介紹了Transactional事務(wù)的使用以及注意說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Spring Boot中整合Spring Security并自定義驗證代碼實例
本篇文章主要介紹了Spring Boot中整合Spring Security并自定義驗證代碼實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04
Java?SE判斷兩個文件內(nèi)容是否相同的多種方法代碼
昨天因為要幫師兄的忙所以看了一下如何判斷兩個文件內(nèi)容是否相同,這里給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于Java?SE判斷兩個文件內(nèi)容是否相同的多種方法,需要的朋友可以參考下2023-11-11

