Memorydoc
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
    • HTML
    • CSS
    • 前端拓展
  • 编程之道

    • 并发编程
    • 设计模式
    • 数据结构算法
    • 技术拓展
    • 技术陷阱
    • 面试宝典
  • 分布式

    • 微服务
    • 数据库
  • 项目优化实战

    • JVM 优化
    • 线程池优化
    • 模板引擎优化
    • 任务调度优化
    • 内存优化
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Memorydoc

术尚可求
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
    • HTML
    • CSS
    • 前端拓展
  • 编程之道

    • 并发编程
    • 设计模式
    • 数据结构算法
    • 技术拓展
    • 技术陷阱
    • 面试宝典
  • 分布式

    • 微服务
    • 数据库
  • 项目优化实战

    • JVM 优化
    • 线程池优化
    • 模板引擎优化
    • 任务调度优化
    • 内存优化
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 并发编程

  • 设计模式

  • 数据结构算法

  • 技术拓展

  • 技术陷阱

  • 面试宝典

  • 微服务

  • 数据库

    • 架构篇
    • Mysql数据库索引原理
    • Mysql数据库索引原理(二)
    • Mybatis 插入数据优化
      • mybatis 一次性插入一万条数据
        • 总结
    • 慢查询
    • 事务
    • 数据库优化
    • Mysql 事务与锁机制
  • 项目优化背景

  • JVM优化

  • 技术架构
  • 数据库
Memorydoc
2022-03-07

Mybatis 插入数据优化原创

# mybatis 一次性插入一万条数据

场景再现

模拟四种场景:(方式四看具体业务场景,如果多线程会对业务产生影响,请不要使用,避免发生不必要的错误)

方式一: 循环insert语句,server层是普通的mybatis插入方法

    //通过循环insert语句插入
    long begin1 = System.currentTimeMillis();

    for (int i1 = 0; i1 < cycleLength; i1++) {
        userService.insert(userList.get(i1));
    }
    long end1 = System.currentTimeMillis();
    System.out.println("通过循环insert语句插入消耗:" + String.valueOf(end1 - begin1));
1
2
3
4
5
6
7
8

方式二: 通过拼接xml语句的方式(推荐使用,但是sql语句长度达到1M的时候会报错,可以在mysql.ini文件中设置大小)

  //通过拼接xml语句的方式 forEach
        long begin2 = System.currentTimeMillis();
        userService.insertForEach(userList);
        long end2 = System.currentTimeMillis();
        System.out.println("通过拼接xml语句的方式 forEach消耗:" + String.valueOf(end2 - begin2));
1
2
3
4
5

对应的xml

     <insert id="insertForEach">
    
        insert into user (id, username, password, address) values
        <foreach collection="lists" item="user" separator=",">
          (#{user.id}, #{user.username}, #{user.password},  #{user.address})
        </foreach>
    
      </insert>
1
2
3
4
5
6
7
8

方式三: 通过SqlSession 的Batch 批处理方式

    //通过mybatis批处理方式
    long begin3 = System.currentTimeMillis();
    userService.insertBatch(userList);
    long end3 = System.currentTimeMillis();
    System.out.println("通过mybatis批处理方式消耗:" + String.valueOf(end3 - begin3));
1
2
3
4
5

对应的servceImpl代码:

    public void insertBatch(List<User> lists) {
            SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            try {
                for (User _user : lists) {
                    mapper.insert(_user);
                }
                //统一提交
                sqlSession.commit();
            } catch (Exception e) {
                //没有提交的数据可以回滚
                sqlSession.rollback();
            } finally {
                //关闭 sqlSession
                sqlSession.close();
            }
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

下面是对应的耗时时间(毫秒):

      方式一:  通过循环insert语句插入消耗:3937
      方式二:  通过拼接xml语句的方式 forEach消耗:114
      方式三:  通过mybatis批处理方式消耗:3187
1
2
3

方式四: 通过 Executors 开启线程池,配合CompletionService 完成多线程并发插入多条数据

提示

这里使用上方得出来的最快的功能即:forEach方法插入批量数据到数据库, 让你体验多线程的魅力 调用方代码


    //通过拼接xml语句的方式 forEach
    long begin2 = System.currentTimeMillis();
    userService.insertForEach(userList);
    long end2 = System.currentTimeMillis();
    System.out.println("通过拼接xml语句的方式 forEach消耗:" + String.valueOf(end2 - begin2));


    //通过拼接xml语句的current方式 forEach
    long begin3 = System.currentTimeMillis();
    userService.insertForEachCurrent(userList);
    long end3 = System.currentTimeMillis();
    System.out.println("通过拼接xml语句的current 的方式 forEach:" + String.valueOf(end3 - begin3));
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14

调用多线程代码

 public void insertForEachCurrent(List<User> lists) {
        DataUtils dataUtils = new DataUtils();
        dataUtils.calcute(lists, 1, 10, userMapper);
    }
1
2
3
4

多线程批量插入(DataUtils 类中code)

    public void calcute(List list, int strategyCode, int threadCount, UserMapper userMapper) {
            ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
            CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(executorService);
            List listNew = groupList(list, 10);
            for (int i = 0; i < threadCount; i++) {
                int finalI = i;
                Callable callable = () -> {
                    return userMapper.insertForEach((List<User>) listNew.get(finalI));
                };
                completionService.submit(callable);
            }
    
            for (int i = 0; i < threadCount; i++) {
                try {
                    completionService.take().get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
        /**
         * @param list  分组数组
         * @param share 要分成多少分
         * @return
         */
        public static List<List<Object>> groupList(List<Object> list, int share) {
            List<List<Object>> listGroup = new ArrayList<List<Object>>();
            int listSize = list.size();
            int totalGroup = list.size() / share;
            int Remaining = (listSize % share);
            for (int i = 0; i < share; i++) {
                List<Object> newList = null;
                if (share - i == 1) {
                    newList = list.subList(i * totalGroup, (i + 1) * totalGroup + Remaining);
                } else {
                    newList = list.subList(i * totalGroup, (i + 1) * totalGroup);
                }
                listGroup.add(newList);
            }
            return listGroup;
        }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

看执行结果:

        方式三: 通过拼接xml语句的方式 forEach消耗:892
        方式四: 通过拼接xml语句的current 的方式 forEach:242

# 总结

通过forEach 循环拼接sql语句是最快的,不知道为啥, 通过mybatis批处理的方式应该也挺快的,但是经过真实测试,竟然没有比循环插入的方式快多少 批处理方式会重复利用mybatis预编译的sql,原理上应该更快的。有点困惑,还请大神指出。
Mybatis内置执行器类型ExecutorType有3种 分别是 ExecutorType.SIMPLE: 不做特殊处理,为每个语句的执行创建一个新的预处理语句。
ExecutorType.REUSE: 可以复用预处理语句。
ExecutorType.BATCH:可以批量执行所有更新语句

SIMPLE与BATCH(批量)对比
默认的是simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql; 而batch模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然batch性能将更优;但是批量模式无法返回自增主键

叮嘱

在能用多线程的情况下,尽量使用多线程。当然没有必要用的时候,也不要用,反正多线程真是魅力无限。可以迅速减少你系统的相应时间和提高 系统吞吐量。尤其是在系统反应很缓慢的时候,数据量太大的时候,可以考虑多线程优化java(在单体,当然可以使用分布式), 同时优化数据,优化jvm

编辑 (opens new window)
上次更新: 2022/03/13, 21:24:24
Mysql数据库索引原理(二)
慢查询

← Mysql数据库索引原理(二) 慢查询→

最近更新
01
命令模式 原创
05-03
02
桥接模式 原创
05-02
03
优雅写代码三 原创
04-29
更多文章>
Theme by Memorydoc | Copyright © 2021-2022 Memorydoc | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式