Service接口
批量新增
批量插入10万条用户数据,并作出对比:
- @Test
- void testSaveOneByOne() {
- long b = System.currentTimeMillis();
- for (int i = 1; i <= 100000; i++) {
- userService.save(buildUser(i));
- }
- long e = System.currentTimeMillis();
- System.out.println("耗时:" + (e - b));
- }
- private User buildUser(int i) {
- User user = new User();
- user.setUsername("user_" + i);
- user.setPassword("123");
- user.setPhone("" + (18688190000L + i));
- user.setBalance(2000);
- user.setInfo("{"age": 24, "intro": "英文老师", "gender": "female"}");
- user.setCreateTime(LocalDateTime.now());
- user.setUpdateTime(user.getCreateTime());
- return user;
- }
复制代码
- @Test
- void testSaveBatch() {
- // 准备10万条数据
- List<User> list = new ArrayList<>(1000);
- long b = System.currentTimeMillis();
- for (int i = 1; i <= 100000; i++) {
- list.add(buildUser(i));
- // 每1000条批量插入一次
- if (i % 1000 == 0) {
- userService.saveBatch(list);
- list.clear();
- }
- }
- long e = System.currentTimeMillis();
- System.out.println("耗时:" + (e - b));
- }
复制代码 实验结果如下:
可以看到利用了批处理以后,比逐条新增效率提高了10倍左右。
检察一下MybatisPlus源码:
- @Transactional(rollbackFor = Exception.class)
- @Override
- public boolean saveBatch(Collection<T> entityList, int batchSize) {
- String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
- return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
- }
- // ...SqlHelper
- public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
- Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
- return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {
- int size = list.size();
- int idxLimit = Math.min(batchSize, size);
- int i = 1;
- for (E element : list) {
- consumer.accept(sqlSession, element);
- if (i == idxLimit) {
- sqlSession.flushStatements();
- idxLimit = Math.min(idxLimit + batchSize, size);
- }
- i++;
- }
- });
- }
复制代码 可以发现其实MybatisPlus的批处理是基于PrepareStatement的预编译模式,然后批量提交,终极在数据库实验时照旧会有多条insert语句,逐条插入数据。SQL类似这样:
- Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
- Parameters: user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01
- Parameters: user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01
- Parameters: user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01
复制代码 而如果想要得到最佳性能,最好是将多条SQL合并为一条,像这样:
- INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
- VALUES
- (user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01),
- (user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01),
- (user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01),
- (user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);
复制代码 在MySQL的客户端连接参数中有这样的一个参数:rewriteBatchedStatements。顾名思义,就是重写批处理的statement语句。这个参数的默认值是false,我们必要修改连接参数,将其配置为true
修改项目中的application.yml文件,在jdbc的url后面添加参数&rewriteBatchedStatements=true:
- spring:
- datasource:
- url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
- driver-class-name: com.mysql.cj.jdbc.Driver
- username: root
- password: MySQL123
复制代码 再次测试插入10万条数据,可以发现速度有非常明显的提拔:
在ClientPreparedStatement的executeBatchInternal中,有判断rewriteBatchedStatements值是否为true并重写SQL的功能。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |