給你 2 萬條數(shù)據(jù),怎么快速導入到 MySQL?寫得太好了!
來源:blog.csdn.net/kisscatforever/article/details/79817039
一、前言
前兩天做了一個導入的功能,導入開始的時候非常慢,導入2w條數(shù)據(jù)要1分多鐘,后來一點一點的優(yōu)化,從直接把list懟進Mysql中,到分配把list導入Mysql中,到多線程把list導入Mysql中。
時間是一點一點的變少了。非常的爽,最后變成了10s以內(nèi)。
下面就展示一下過程。
二、直接把list懟進Mysql
@Transactional(rollbackFor?=?Exception.class)
public?int?addFreshStudentsNew2(List?list,?String?schoolNo)?{
????if?(list?==?null?||?list.isEmpty())?{
????????return?0;
????}
????List?studentEntityList?=?new?LinkedList<>();
????List?enrollStudentEntityList?=?new?LinkedList<>();
????List?allusersEntityList?=?new?LinkedList<>();
????for?(FreshStudentAndStudentModel?freshStudentAndStudentModel?:?list)?{
????????EnrollStudentEntity?enrollStudentEntity?=?new?EnrollStudentEntity();
????????StudentEntity?studentEntity?=?new?StudentEntity();
????????BeanUtils.copyProperties(freshStudentAndStudentModel,?studentEntity);
????????BeanUtils.copyProperties(freshStudentAndStudentModel,?enrollStudentEntity);
????????String?operator?=?TenancyContext.UserID.get();
????????String?studentId?=?BaseUuidUtils.base58Uuid();
????????enrollStudentEntity.setId(BaseUuidUtils.base58Uuid());
????????enrollStudentEntity.setStudentId(studentId);
????????enrollStudentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
????????enrollStudentEntity.setOperator(operator);
????????studentEntity.setId(studentId);
????????studentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
????????studentEntity.setOperator(operator);
????????studentEntityList.add(studentEntity);
????????enrollStudentEntityList.add(enrollStudentEntity);
????????AllusersEntity?allusersEntity?=?new?AllusersEntity();
????????allusersEntity.setId(enrollStudentEntity.getId());
????????allusersEntity.setUserCode(enrollStudentEntity.getNemtCode());
????????allusersEntity.setUserName(enrollStudentEntity.getName());
????????allusersEntity.setSchoolNo(schoolNo);
????????allusersEntity.setTelNum(enrollStudentEntity.getTelNum());
????????allusersEntity.setPassword(enrollStudentEntity.getNemtCode());??//密碼設(shè)置為考生號
????????allusersEntityList.add(allusersEntity);
????}
????????enResult?=?enrollStudentDao.insertAll(enrollStudentEntityList);
????????stuResult?=?studentDao.insertAll(studentEntityList);
????????allResult?=?allusersFacade.insertUserList(allusersEntityList);
????if?(enResult?>?0?&&?stuResult?>?0?&&?allResult)?{
????????return?10;
????}
????return?-10;
}
推薦一個 Spring Boot 基礎(chǔ)教程及實戰(zhàn)示例:
https://github.com/javastacks/spring-boot-best-practice
Mapper.xml
"insertAll"?parameterType="com.dmsdbj.itoo.basicInfo.entity.EnrollStudentEntity">
????insert?into?tb_enroll_student
????"("?suffix=")"?suffixOverrides=",">
????????????id,??
????????????remark,??
????????????nEMT_aspiration,??
????????????nEMT_code,??
????????????nEMT_score,??
????????????student_id,??
????????????identity_card_id,??
????????????level,??
????????????major,??
????????????name,??
????????????nation,??
????????????secondary_college,??
????????????operator,??
????????????sex,??
????????????is_delete,??
????????????account_address,??
????????????native_place,??
????????????original_place,??
????????????used_name,??
????????????pictrue,??
????????????join_party_date,??
????????????political_status,??
????????????tel_num,??
????????????is_registry,??
????????????graduate_school,??
????????????create_time,??
????????????update_time???????? ????????
????values
????"list"?item="item"?index="index"?separator=",">
????(
????????????#{item.id,jdbcType=VARCHAR},
????????????#{item.remark,jdbcType=VARCHAR},
????????????#{item.nemtAspiration,jdbcType=VARCHAR},
????????????#{item.nemtCode,jdbcType=VARCHAR},
????????????#{item.nemtScore,jdbcType=VARCHAR},
????????????#{item.studentId,jdbcType=VARCHAR},
????????????#{item.identityCardId,jdbcType=VARCHAR},
????????????#{item.level,jdbcType=VARCHAR},
????????????#{item.major,jdbcType=VARCHAR},
????????????#{item.name,jdbcType=VARCHAR},
????????????#{item.nation,jdbcType=VARCHAR},
????????????#{item.secondaryCollege,jdbcType=VARCHAR},
????????????#{item.operator,jdbcType=VARCHAR},
????????????#{item.sex,jdbcType=VARCHAR},
????????????0,
????????????#{item.accountAddress,jdbcType=VARCHAR},
????????????#{item.nativePlace,jdbcType=VARCHAR},
????????????#{item.originalPlace,jdbcType=VARCHAR},
????????????#{item.usedName,jdbcType=VARCHAR},
????????????#{item.pictrue,jdbcType=VARCHAR},
????????????#{item.joinPartyDate,jdbcType=VARCHAR},
????????????#{item.politicalStatus,jdbcType=VARCHAR},
????????????#{item.telNum,jdbcType=VARCHAR},
????????????#{item.isRegistry,jdbcType=TINYINT},
????????????#{item.graduateSchool,jdbcType=VARCHAR},
????????????now(),
????????????now()????????
????)???
???? ????????????????
?
底層的mapper是通過逆向工程來生成的,批量插入如下,是拼接成類似:insert into tb_enroll_student()values (),()…….();
關(guān)注Java項目精選,推送更多 Java 干貨!
這樣的缺點是,數(shù)據(jù)庫一般有一個默認的設(shè)置,就是每次sql操作的數(shù)據(jù)不能超過4M。這樣插入,數(shù)據(jù)多的時候,數(shù)據(jù)庫會報錯Packet for query is too large (6071393 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.,雖然我們可以通過
類似 修改 my.ini 加上 max_allowed_packet =67108864,67108864=64M,默認大小4194304 也就是4M
修改完成之后要重啟mysql服務(wù),如果通過命令行修改就不用重啟mysql服務(wù)。
完成本次操作,但是我們不能保證項目單次最大的大小是多少,這樣是有弊端的。所以可以考慮進行分組導入。
三、分組把list導入Mysql中
同樣適用mybatis批量插入,區(qū)別是對每次的導入進行分組計算,然后分多次進行導入:
@Transactional(rollbackFor?=?Exception.class)
public?int?addFreshStudentsNew2(List?list,?String?schoolNo)?{
????if?(list?==?null?||?list.isEmpty())?{
????????return?0;
????}
????List?studentEntityList?=?new?LinkedList<>();
????List?enrollStudentEntityList?=?new?LinkedList<>();
????List?allusersEntityList?=?new?LinkedList<>();
????for?(FreshStudentAndStudentModel?freshStudentAndStudentModel?:?list)?{
????????EnrollStudentEntity?enrollStudentEntity?=?new?EnrollStudentEntity();
????????StudentEntity?studentEntity?=?new?StudentEntity();
????????BeanUtils.copyProperties(freshStudentAndStudentModel,?studentEntity);
????????BeanUtils.copyProperties(freshStudentAndStudentModel,?enrollStudentEntity);
????????String?operator?=?TenancyContext.UserID.get();
????????String?studentId?=?BaseUuidUtils.base58Uuid();
????????enrollStudentEntity.setId(BaseUuidUtils.base58Uuid());
????????enrollStudentEntity.setStudentId(studentId);
????????enrollStudentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
????????enrollStudentEntity.setOperator(operator);
????????studentEntity.setId(studentId);
????????studentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
????????studentEntity.setOperator(operator);
????????studentEntityList.add(studentEntity);
????????enrollStudentEntityList.add(enrollStudentEntity);
????????AllusersEntity?allusersEntity?=?new?AllusersEntity();
????????allusersEntity.setId(enrollStudentEntity.getId());
????????allusersEntity.setUserCode(enrollStudentEntity.getNemtCode());
????????allusersEntity.setUserName(enrollStudentEntity.getName());
????????allusersEntity.setSchoolNo(schoolNo);
????????allusersEntity.setTelNum(enrollStudentEntity.getTelNum());
????????allusersEntity.setPassword(enrollStudentEntity.getNemtCode());??//密碼設(shè)置為考生號
????????allusersEntityList.add(allusersEntity);
????}
????int?c?=?100;
????int?b?=?enrollStudentEntityList.size()?/?c;
????int?d?=?enrollStudentEntityList.size()?%?c;
????int?enResult?=?0;
????int?stuResult?=?0;
????boolean?allResult?=?false;
????for?(int?e?=?c;?e?<=?c?*?b;?e?=?e?+?c)?{
????????enResult?=?enrollStudentDao.insertAll(enrollStudentEntityList.subList(e?-?c,?e));
????????stuResult?=?studentDao.insertAll(studentEntityList.subList(e?-?c,?e));
????????allResult?=?allusersFacade.insertUserList(allusersEntityList.subList(e?-?c,?e));
????}
????if?(d?!=?0)?{
????????enResult?=?enrollStudentDao.insertAll(enrollStudentEntityList.subList(c?*?b,?enrollStudentEntityList.size()));
????????stuResult?=?studentDao.insertAll(studentEntityList.subList(c?*?b,?studentEntityList.size()));
????????allResult?=?allusersFacade.insertUserList(allusersEntityList.subList(c?*?b,?allusersEntityList.size()));
????}
????if?(enResult?>?0?&&?stuResult?>?0?&&?allResult)?{
????????return?10;
????}
????return?-10;
}
這樣操作,可以避免上面的錯誤,但是分多次插入,無形中就增加了操作實踐,很容易超時。所以這種方法還是不值得提倡的。
再次改進,使用多線程分批導入。
四、多線程分批導入Mysql
依然使用mybatis的批量導入,不同的是,根據(jù)線程數(shù)目進行分組,然后再建立多線程池,進行導入。
@Transactional(rollbackFor?=?Exception.class)
public?int?addFreshStudentsNew(List?list,?String?schoolNo)?{
????if?(list?==?null?||?list.isEmpty())?{
????????return?0;
????}
????List?studentEntityList?=?new?LinkedList<>();
????List?enrollStudentEntityList?=?new?LinkedList<>();
????List?allusersEntityList?=?new?LinkedList<>();
????list.forEach(freshStudentAndStudentModel?->?{
????????EnrollStudentEntity?enrollStudentEntity?=?new?EnrollStudentEntity();
????????StudentEntity?studentEntity?=?new?StudentEntity();
????????BeanUtils.copyProperties(freshStudentAndStudentModel,?studentEntity);
????????BeanUtils.copyProperties(freshStudentAndStudentModel,?enrollStudentEntity);
????????String?operator?=?TenancyContext.UserID.get();
????????String?studentId?=?BaseUuidUtils.base58Uuid();
????????enrollStudentEntity.setId(BaseUuidUtils.base58Uuid());
????????enrollStudentEntity.setStudentId(studentId);
????????enrollStudentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
????????enrollStudentEntity.setOperator(operator);
????????studentEntity.setId(studentId);
????????studentEntity.setIdentityCardId(freshStudentAndStudentModel.getIdCard());
????????studentEntity.setOperator(operator);
????????studentEntityList.add(studentEntity);
????????enrollStudentEntityList.add(enrollStudentEntity);
????????AllusersEntity?allusersEntity?=?new?AllusersEntity();
????????allusersEntity.setId(enrollStudentEntity.getId());
????????allusersEntity.setUserCode(enrollStudentEntity.getNemtCode());
????????allusersEntity.setUserName(enrollStudentEntity.getName());
????????allusersEntity.setSchoolNo(schoolNo);
????????allusersEntity.setTelNum(enrollStudentEntity.getTelNum());
????????allusersEntity.setPassword(enrollStudentEntity.getNemtCode());??//密碼設(shè)置為考生號
????????allusersEntityList.add(allusersEntity);
????});
????int?nThreads?=?50;
????int?size?=?enrollStudentEntityList.size();
????ExecutorService?executorService?=?Executors.newFixedThreadPool(nThreads);
????List>?futures?=?new?ArrayList>(nThreads);
????for?(int?i?=?0;?i?????????final?List?EnrollStudentEntityImputList?=?enrollStudentEntityList.subList(size?/?nThreads?*?i,?size?/?nThreads?*?(i?+?1));
????????final?List?studentEntityImportList?=?studentEntityList.subList(size?/?nThreads?*?i,?size?/?nThreads?*?(i?+?1));
????????final?List?allusersEntityImportList?=?allusersEntityList.subList(size?/?nThreads?*?i,?size?/?nThreads?*?(i?+?1));
???????Callable?task1?=?()?->?{
??????studentSave.saveStudent(EnrollStudentEntityImputList,studentEntityImportList,allusersEntityImportList);
???????????return?1;
????????};
??????futures.add(executorService.submit(task1));
????}
????executorService.shutdown();
????if?(!futures.isEmpty()?&&?futures?!=?null)?{
????????return?10;
????}
????return?-10;
}
缺點就是要看服務(wù)器的配置,如果配置好的話就可以開多點線程,配置差的話就開小點。
五、小結(jié)
通過使用這個操作真是不斷的提高了,項目使用技巧也是不錯。
加油~~ 多線程哦~~
如有文章對你有幫助,
“在看”和轉(zhuǎn)發(fā)是對我最大的支持!
暫時開放微信大號,好友位不多,需要的小伙伴們可以加,朋友圈和交流群里會發(fā)一些學習資料、個人見解、白嫖課程等等。 掃描下方二維碼即可加我微信啦,2022,抱團取暖,一起牛逼。

