MyBatis實(shí)現(xiàn)插入大量數(shù)據(jù)方法詳解
1、前言
在開發(fā)過程中,有時(shí)我們會(huì)碰到將大批量的數(shù)據(jù)入庫的場景,那么我們一般有下面三種方式入庫:
- ExecutorType.BATCH批處理方式插入
- foreach循環(huán)標(biāo)簽插入
- MyBatisPlus自帶的saveBatch批量新增方法
下面我們用一個(gè)案例來測試一下,看下三種方式哪種效率最好
2、案例說明
現(xiàn)在我數(shù)據(jù)庫新建一張表t_user,建表語句如下:
DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID', `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名', `age` int(11) NULL DEFAULT NULL COMMENT '年齡', `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手機(jī)號(hào)', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用戶表' ROW_FORMAT = DYNAMIC;
我這里MySQL數(shù)據(jù)庫版本是5.5.28
然后我用上面三種方式分別向t_user表中存5萬條數(shù)據(jù),每次存數(shù)據(jù)前,先清空一下表數(shù)據(jù),使用下面語句快速清除表數(shù)據(jù):
truncate table t_user;
每種方式我都測試5次,然后每種方式我都計(jì)算一個(gè)耗時(shí)平均值,看哪種方式耗時(shí)最小
3、編碼
這是我的基礎(chǔ)項(xiàng)目:https://gitee.com/colinWu_java/spring-boot-base.git
我會(huì)在此基礎(chǔ)項(xiàng)目上做測試
3.1、ExecutorType.BATCH批處理方式插入
ExecutorType.BATCH介紹:
- Mybatis內(nèi)置的ExecutorType有3種,SIMPLE、REUSE、BATCH,默認(rèn)的是simple,該模式下它為每個(gè)語句的執(zhí)行創(chuàng)建一個(gè)新的預(yù)處理語句,單條提交sql
- 而batch模式重復(fù)使用已經(jīng)預(yù)處理的語句,并且批量執(zhí)行所有更新語句,顯然batch性能將更優(yōu);但batch模式也有自己的問題,比如在Insert操作時(shí),在事務(wù)沒有提交之前,是沒有辦法獲取到自增的id,這在某型情形下是不符合業(yè)務(wù)要求的
- 通過批處理的方式,我們就可以在 JDBC 客戶端緩存多條 SQL 語句,然后在 flush 或緩存滿的時(shí)候,將多條 SQL 語句打包發(fā)送到數(shù)據(jù)庫執(zhí)行,這樣就可以有效地降低上述兩方面的損耗,從而提高系統(tǒng)性能
下面開始編碼
在UserController新增下面接口
/** * 測試大批量數(shù)據(jù)插入數(shù)據(jù)庫 * 方式1:用一個(gè) for 循環(huán),把數(shù)據(jù)一條一條的插入 * @return */ @GetMapping("/insertUser1/{count}") public JSONResult insertUser1(@PathVariable("count") Integer count){ return userService.insertUser1(count); }
userService代碼:
public JSONResult insertUser1(Integer count) { //如果自動(dòng)提交設(shè)置為true,將無法控制提交的條數(shù)。所以我這里設(shè)置為false,,改為統(tǒng)一提交 SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false); UserMapper uMapper = session.getMapper(UserMapper.class); //獲取測試用戶集合數(shù)據(jù) List<User> userList = Tools.getUserList(count); int commitCount = 5000;//每次提交的數(shù)量條數(shù) long startTime = System.currentTimeMillis(); for(int i=0; i<userList.size(); i++){ uMapper.addUserOne(userList.get(i)); if (i != 0 && i % commitCount == 0) { session.commit(); } } session.commit(); long endTime = System.currentTimeMillis(); log.info("方式1耗時(shí):{}", (endTime - startTime)); return JSONResult.success(); }
在UserMapper中新增addUserOne方法:
Integer addUserOne(User user);
對應(yīng)xml代碼:
<insert id="addUserOne"> insert into t_user (name, age, phone) values (#{name}, #{age}, #{phone}) </insert>
getUserList方法代碼如下,就是獲取指定數(shù)量的測試用戶數(shù)據(jù)而已:
/** * 獲取指定數(shù)量的用戶測試對象 * @param count 數(shù)量 * @return */ public static List<User> getUserList(int count){ List<User> userList = new ArrayList<>(); User user = null; for(int i=1; i<=count; i++){ user = new User(); user.setName("王天霸" + i + "號(hào)"); user.setAge(i); user.setAge(i); user.setPhone("18022222222"); userList.add(user); } return userList; }
注意事項(xiàng):
如果單次提交給MySQL數(shù)據(jù)庫的數(shù)據(jù)量太大的話,可能會(huì)報(bào)如下錯(cuò)誤:
com.mysql.jdbc.PacketTooBigException: Packet for query is too large (204444558 > 1048576). You can change this value on the server by setting the max_allowed_packet' variable.
出現(xiàn)這個(gè)錯(cuò)誤的原因是:
大數(shù)據(jù)量的插入或更新會(huì)被 max_allowed_packet 參數(shù)限制,導(dǎo)致失敗,因?yàn)镸ySQL根據(jù)配置文件會(huì)限制server允許接收的數(shù)據(jù)包大小
解決方案:
修改MySQL的配置參數(shù)
打開Mysql命令客戶端:
然后輸入密碼之后輸入下面命令:
show VARIABLES like '%max_allowed_packet%';
結(jié)果:
用這個(gè)值除以1024*1024等于200M,那我這里就是設(shè)置的200M,根據(jù)實(shí)際情況設(shè)置該值,執(zhí)行如下命令即可:
set global max_allowed_packet = 200*1024*1024;
設(shè)置完成之后,需要關(guān)閉MySQL服務(wù),然后再重啟(注意,這里最大設(shè)置為:1G)
重啟成功后,需要關(guān)掉上面的MySQL命令客戶端,再重新打開一次,查看是否設(shè)置成功
注意:
- MySQL的JDBC連接的url中要加rewriteBatchedStatements參數(shù),并保證5.1.13以上版本的驅(qū)動(dòng),才能實(shí)現(xiàn)高性能的批量插入
- MySQL JDBC驅(qū)動(dòng)在默認(rèn)情況下會(huì)無視executeBatch()語句,把我們期望批量執(zhí)行的一組SQL語句拆散,一條一條地發(fā)給MySQL數(shù)據(jù)庫,批量插入實(shí)際上是單條插入,直接造成較低的性能
- 只有把rewriteBatchedStatements參數(shù)置為true,驅(qū)動(dòng)才會(huì)幫你批量執(zhí)行SQL,另外這個(gè)選項(xiàng)對INSERT/UPDATE/DELETE都有效
url: jdbc:mysql://127.0.0.1:3306/test1?allowMultiQueries=true&rewriteBatchedStatements=true
//allowMultiQueries=true,允許一次性執(zhí)行多條SQL,批量插入時(shí)必須在連接地址后面加allowMultiQueries=true這個(gè)參數(shù)
//rewriteBatchedStatements=true,批量將數(shù)據(jù)傳給MySQL,數(shù)據(jù)庫會(huì)更高性能的執(zhí)行批量處理,MySQL數(shù)據(jù)庫版本在5.1.13以上,才能實(shí)現(xiàn)高性能的批量插入
3.2、foreach循環(huán)標(biāo)簽插入
UserController新增方法:
/** * 測試大批量數(shù)據(jù)插入數(shù)據(jù)庫 * 方式2:foreach標(biāo)簽批量插入 * @return */ @GetMapping("/insertUser2/{count}") public JSONResult insertUser2(@PathVariable("count") Integer count){ return userService.insertUser2(count); }
insertUser2代碼:
public JSONResult insertUser2(Integer count) { //獲取測試用戶集合數(shù)據(jù) List<User> userList = Tools.getUserList(count); int countItem = 5000;//每次提交的記錄條數(shù) int userSize = userList.size(); List<User> userListTemp = new ArrayList<>(); long startTime = System.currentTimeMillis(); for (int i = 0, n=userSize; i < n; i++) { User user= userList.get(i); userListTemp.add(user); if ((i>0 && i % countItem == 0) || i == userSize - 1) { //每5000條記錄提交一次 userMapper.insertUserBatch(userListTemp); userListTemp.clear(); } } long endTime = System.currentTimeMillis(); log.info("方式2耗時(shí):{}", (endTime - startTime)); return JSONResult.success(); }
UserMapper的insertUserBatch方法:
void insertUserBatch(@Param("userList") List<User> userList);
對應(yīng)xml代碼:
<insert id="insertUserBatch"> insert into t_user (name, age, phone) values <foreach collection="userList" item="user" separator=","> (#{user.name}, #{user.age}, #{user.phone}) </foreach> </insert>
3.3、MyBatisPlus自帶的saveBatch批量新增方法
UserController新增方法:
@Autowired public UserService userService; /** * 測試大批量數(shù)據(jù)插入數(shù)據(jù)庫 * 方式3:mybatisplus自帶的saveBatch批量新增方法 * @return */ @GetMapping("/insertUser3/{count}") public JSONResult insertUser3(@PathVariable("count") Integer count){ //獲取測試用戶集合數(shù)據(jù) List<User> userList = Tools.getUserList(count); long startTime = System.currentTimeMillis(); userService.saveBatch(userList, 5000);//每次往數(shù)據(jù)庫提交5000條數(shù)據(jù) long endTime = System.currentTimeMillis(); log.info("方式3耗時(shí):{}", (endTime - startTime)); return JSONResult.success(); }
4、測試
在瀏覽器中訪問地址:http://localhost:8001/user/insertUser1/50000
這里的50000是表示插入5萬條數(shù)據(jù)到t_user表
每次訪問后,都記錄一下耗時(shí),然后清掉t_user表數(shù)據(jù),再訪問,反復(fù)測試5次
然后再訪問http://localhost:8001/user/insertUser2/50000和http://localhost:8001/user/insertUser2/50000
步驟和上面一致,最終我統(tǒng)計(jì)出來的結(jié)果如下:
很明顯,方式一效率最高
注意:以上結(jié)果僅是我本地測試情況,大家機(jī)器上可能會(huì)不同
5、總結(jié)
經(jīng)過今天的測試,我們知道批量插入數(shù)據(jù)最快的方式就是ExecutorType.BATCH批處理方式插入有些小的注意點(diǎn)記得在實(shí)際開發(fā)中謹(jǐn)慎處理,比如url參數(shù)配置,還有提交數(shù)量不要太大
到此這篇關(guān)于MyBatis實(shí)現(xiàn)插入大量數(shù)據(jù)方法詳解的文章就介紹到這了,更多相關(guān)MyBatis插入大量數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MyBatis批量插入幾千條數(shù)據(jù)為何慎用foreach
- SpringBoot Mybatis批量插入Oracle數(shù)據(jù)庫數(shù)據(jù)
- Mybatis?Plus插入數(shù)據(jù)后獲取新數(shù)據(jù)id值的踩坑記錄
- 如何用注解的方式實(shí)現(xiàn)Mybatis插入數(shù)據(jù)時(shí)返回自增的主鍵Id
- mybatis插入數(shù)據(jù)后如何返回新增數(shù)據(jù)的id值
- MyBatis?實(shí)現(xiàn)多對多中間表插入數(shù)據(jù)
- MyBatis實(shí)現(xiàn)批量插入數(shù)據(jù),多重forEach循環(huán)
- Mybatis如何獲取最新插入數(shù)據(jù)的id
相關(guān)文章
Java實(shí)現(xiàn)排球比賽計(jì)分系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)排球比賽計(jì)分系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Java中4種校驗(yàn)注解詳解(值校驗(yàn)、范圍校驗(yàn)、長度校驗(yàn)、格式校驗(yàn))
這篇文章主要給大家介紹了關(guān)于Java中4種校驗(yàn)注解詳解的相關(guān)資料,分別包括值校驗(yàn)、范圍校驗(yàn)、長度校驗(yàn)、格式校驗(yàn)等,Java注解(Annotation)是一種元數(shù)據(jù),它可以被添加到Java代碼中,并可以提供額外的信息和指令,需要的朋友可以參考下2023-08-08Tornadofx學(xué)習(xí)筆記之IconTextFx開源庫整合5000+個(gè)字體圖標(biāo)
這篇文章主要介紹了Tornadofx學(xué)習(xí)筆記之IconTextFx開源庫整合5000+個(gè)字體圖標(biāo)的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12使用Java實(shí)現(xiàn)百萬Excel數(shù)據(jù)導(dǎo)出
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)百萬Excel數(shù)據(jù)導(dǎo)出,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下2024-03-03MyBatis?詳細(xì)講解動(dòng)態(tài)?SQL的使用
動(dòng)態(tài)?SQL?是?MyBatis?的強(qiáng)大特性之一。如果你使用過?JDBC?或其它類似的框架,你應(yīng)該能理解根據(jù)不同條件拼接?SQL?語句有多痛苦,例如拼接時(shí)要確保不能忘記添加必要的空格,還要注意去掉列表最后一個(gè)列名的逗號(hào)。利用動(dòng)態(tài)?SQL,可以徹底擺脫這種痛苦2022-04-04