Mybatis-Plus多種批量插入方案對(duì)比小結(jié)
背景
六月某日上線了一個(gè)日?qǐng)?bào)表任務(wù),因是第一次上線,故需要為歷史所有日期都初始化一次報(bào)表數(shù)據(jù)
在執(zhí)行過(guò)程中發(fā)現(xiàn)新增特別的慢:插入十萬(wàn)條左右的數(shù)據(jù),SQL執(zhí)行耗費(fèi)高達(dá)三分多鐘
因很早就聽聞過(guò)mybatis-plus的[偽]批量新增的問(wèn)題,很快鎖定問(wèn)題并進(jìn)行修復(fù),下面細(xì)節(jié)描述多種批量新增方案的具體性能表現(xiàn)
# 測(cè)試表結(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字段給的長(zhǎng)度大了些,模擬生產(chǎn)實(shí)際表結(jié)構(gòu)占用 # 測(cè)試庫(kù)版本: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("中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)"); testUser.setAge(20); testUserService.save(testUser); } System.out.println("毫秒==>" + (System.currentTimeMillis() - l)); } # 插入10萬(wàn)條耗時(shí):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("中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)"); testUser.setAge(20); list.add(testUser); } testUserService.saveBatch(list); System.out.println("毫秒==>" + (System.currentTimeMillis() - l)); } # 插入耗時(shí):62180ms,大約1分鐘
這里先留下一張saveBatch的SQL日志截圖,這里的日志是一個(gè)insert into語(yǔ)句,下面帶了一千條數(shù)據(jù)(MP默認(rèn)一千條一個(gè)批次),使用Mybatis Log插件查看還是單條的SQL
方案三:在方案二的基礎(chǔ)上修改MySQL連接參數(shù):rewriteBatchedStatements=true
# 與方案二測(cè)試代碼相同 # 插入耗時(shí):35260ms,大約半分鐘
其SQL日志也如上方案二所示,MySQL Jdbc驅(qū)動(dòng)在默認(rèn)情況下會(huì)無(wú)視executeBatch()語(yǔ)句,把我們期望批量執(zhí)行的一組sql語(yǔ)句拆散,一條一條地發(fā)給MySQL數(shù)據(jù)庫(kù),直接造成較低的性能
Mysql連接配置鏈接
方案四:使用Mybatis-Plus提供的擴(kuò)展插件: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("中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)中國(guó)"); testUser.setAge(20); list.add(testUser); // 因?yàn)閙ysql的參數(shù)max_allowed_packet限制,所以這里程序改成單批1000條 if(list.size() == 1000){ testUserMapper.insertBatchSomeColumn(list); list.clear(); } } System.out.println("毫秒==>" + (System.currentTimeMillis() - l)); } # 插入耗時(shí):24410ms,大約24秒
再來(lái)看看此時(shí)控制臺(tái)的SQL,與方案二的SQL有著明顯的區(qū)別,這里是將批次插入的數(shù)據(jù)拼接在同一條SQL中,對(duì)于MySQL處理來(lái)說(shuō),這是真的批量新增
配置方式
// 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. 實(shí)現(xiàn)自定義baseMapper public interface BatchSaveBaseMapper<T> extends BaseMapper<T> { /** * 批量插入 僅適用于mysql * * @param entityList * 實(shí)體列表 * @return 影響行數(shù) */ Integer insertBatchSomeColumn(Collection<T> entityList); } // 3. 注入插件 // 方式一 @Configuration public class MybatisPlusConfig { /** * 分頁(yè)插件 */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true)); return paginationInterceptor; } /** * SQL注入器 */ @Bean public BatchSaveSqlInjector easySqlInjector() { return new BatchSaveSqlInjector(); } } // 方式二 // 若項(xiàng)目有自定義SqlSessionFactory,也可在初始化時(shí)將自定義SQL注入器植入,參考下圖MybatisPlusAutoConfiguration.sqlSessionFactory的做法
總結(jié)
插入10萬(wàn)數(shù)據(jù)耗時(shí)(秒) | |
---|---|
mybatis-plus:save | 238 |
mybatis-plus的saveBatch:rewriteBatchedStatements=false | 62 |
mybatis-plus的saveBatch:rewriteBatchedStatements=true | 35 |
mybatis-plus擴(kuò)展插件:InsertBatchSomeColumn | 24 |
到此這篇關(guān)于Mybatis-Plus多種批量插入方案對(duì)比小結(jié)的文章就介紹到這了,更多相關(guān)Mybatis-Plus多種批量插入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java線程池ExecutorService超時(shí)處理小結(jié)
使用ExecutorService時(shí),設(shè)置子線程執(zhí)行超時(shí)是一個(gè)常見(jiàn)需求,本文就來(lái)詳細(xì)的介紹一下ExecutorService超時(shí)的三種方法,感興趣的可以了解一下2024-09-09基于Transactional事務(wù)的使用以及注意說(shuō)明
這篇文章主要介紹了Transactional事務(wù)的使用以及注意說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07java實(shí)現(xiàn)簡(jiǎn)單發(fā)送郵件功能
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單發(fā)送郵件功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Spring Boot中整合Spring Security并自定義驗(yàn)證代碼實(shí)例
本篇文章主要介紹了Spring Boot中整合Spring Security并自定義驗(yàn)證代碼實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04Java?SE判斷兩個(gè)文件內(nèi)容是否相同的多種方法代碼
昨天因?yàn)橐獛蛶熜值拿λ钥戳艘幌氯绾闻袛鄡蓚€(gè)文件內(nèi)容是否相同,這里給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于Java?SE判斷兩個(gè)文件內(nèi)容是否相同的多種方法,需要的朋友可以參考下2023-11-11