yeskery

MyBatis 的批量插入

拼接 SQL 的方式

  1. 在 mapper 文件中写一个 insertBatch 方法。
  1. <insert id="insertBatch">
  2. insert into user(name,age,is_delete)
  3. <foreach collection="users" item="user" open="VALUES" close=";" separator=",">
  4. (#{user.name},#{user.age},#{user.isDelete})
  5. </foreach>
  6. </insert>
  1. 在接口中声明方法
  1. public interface IUserMapper {
  2. void insertBatch(@Param("users") List<User> users);
  3. }
  1. 编写测试方法
  1. @Test
  2. public void test08(){
  3. SqlSession sqlSession = getSqlSession();
  4. IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
  5. List<User> users=new ArrayList<>();
  6. for(int i=0;i<10000;i++){
  7. users.add(new User(null,"赵敏"+i,i,"镇雄"+i,false));
  8. }
  9. long begin=System.currentTimeMillis();
  10. int insertBatch = mapper.insertBatch(users);
  11. long end=System.currentTimeMillis();
  12. System.out.println(end-begin);
  13. System.out.println(insertBatch);
  14. sqlSession.close();
  15. }

注:这种实现方式非正解,测试了1w条数据的时候差不多需要一秒多时间,可是当改成 10w 条的时候,直接卡着不会动了。这种方式是把所有数据拼成一条 SQL 传递给数据库,这种方式,当 SQL 长时是存在问题的,而且,这种方式也只限于 MySql,Oracle 不支持这种方式。

使用 ExecutorType.BATCH 创建 SqlSession

  1. 测试代码
  1. @Test
  2. public void test09(){
  3. SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
  4. IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
  5. long begin=System.currentTimeMillis();
  6. for(int i=0;i<10000;i++){
  7. mapper.insert(new User(null,"赵敏"+i,i,"镇雄"+i,false));
  8. }
  9. long end=System.currentTimeMillis();
  10. System.out.println(end-begin);
  11. sqlSession.close();
  12. }

注:这种方式在测试1w条的时候虽然看起来比拼接 SQL 慢一点,但在插入 10w 条的时候不会卡死,差不多就是插入1w条的10倍,所以时间复杂度是 O(n)。推荐使用 mybatis 提供的 ExcecutorType.BATCH。

在 SpringBoot 中使用

实际上,大数据量插入主要耗时在session的频繁开启。因此一起开启,集中提交会大幅提升数据插入速度。

  1. @Autowired
  2. private SqlSessionTemplate sqlSessionTemplate;//引入bean
  3. public void batchInsert(List<Fenshu> fenshuList, Date current, String tableName, String courseName){
  4. SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);//关闭session的自动提交
  5. excelMapper = session.getMapper(ExcelMapper.class);//利用反射生成mapper对象
  6. try {
  7. int i=0;
  8. for (Fenshu fs : fenshuList) {
  9. excelMapper.saveFenshu(tableName, courseName, current, fs.getXuehao(), fs.getShijuanming(), fs.getDenglushijian(), fs.getJiaojuanshijian(),
  10. fs.getShitileixing(), fs.getShitixuhao(), fs.getShititikuhao(), fs.getShitifenzhi(),
  11. fs.getXueshengdefen(), fs.getDatiyongshi());
  12. if (i % 1000 == 0 || i == fenshuList.size()-1) {
  13. //手动每1000个一提交,提交后无法回滚
  14. session.commit();
  15. session.clearCache();//注意,如果没有这个动作,可能会导致内存崩溃。
  16. }
  17. i++;
  18. }
  19. }catch (Exception e) {
  20. //没有提交的数据可以回滚
  21. session.rollback();
  22. } finally{
  23. session.close();
  24. }
  25. }

下面就几个底层问题说一下自己的理解:

  1. 代码中的 session 还是指 MyBatis的 session,用途使用方式应该与 MySQL 里面的 session 一致的,但是 session 对象还是存储在web服务器内存而不是数据库内存。因为 session 本身就是数据库的客户端对象,所以可以理解为客户端对象存在了web服务器中。

  2. 本人理解的实现原理应该是这样的:web 服务器开启 session,此时会新建 MySQL 数据库的 session,web 服务器里面的 session 不断获取批量插入对象存储在 web 服务器内存,直到 session.commit。当 commit 时,web服务器里面的数据库客户端对象会把批量数据,发送给数据库服务器,然后数据库执行批量插入。也就是说,web服务器内存的对象有可能会在断电后丢失,未能存入数据库服务器。

MySQL 数据库配置

我们可以在数据库配置文件做一些修改进一步提升批量插入性能,配置文件修改如下:

  1. bulk_insert_buffer_size=120M
  2. Max_allowed_packet=20M
  3. [mysqldump]
  4. Net_buffer_length=2k

bulk_insert_buffer_size:如果我们需要向一个非空表中插入数据,增加这个缓存的大小会提升插入速度。本项目中,我们修改这个参数,主要是为了改善批量插入大表的性能。

Max_allowed_packet:这个参数决定着客户端每次向数据库发送的包的大小。本项目中,为防止我们每次提交批量数据,导致发包的大小超过数据库限制,所以我设置了一个适应我项目的参数大小。

Net_buffer_length:该处参数主要调整服务器接收语句长度的大小,测试中对数据库性能影响不大。

本文内容来自:

  1. https://my.oschina.net/zhaomin/blog/1587816
  2. https://blog.csdn.net/songjianyue12345/article/details/78774011

评论

发表评论 点击刷新验证码

提示

该功能暂未开放