多线程分批处理数据及MyBatis的批量插入

一、背景

数据量较多时,我们常常遇到需要分批处理的情况,比如上千上万数据需要需要操作数据库时(入库或者更新),我们想到分批处理,或者解析文件数据量较多,我们可能多线程分批处理,每个线程执行一定的批次数据等…
不管何种场景,当我们想到分批处理时,那如何分批,怎么计算批次数呢?

tip:此处以小数据量为例:假设待处理数据总条数75,每10条数据一处理

我常用的方法总结两种供参考:

  • 方法一:循环遍历待处理的处理,当到达待处理的批次或批次倍数,或最后不足一次剩余的,满足条件即执行一次分批处理操作。eg:10,20,30…7
  • 方法二:根据总数计算总批次数(75/10+1=8次),每次从待处理数据集中截取要处理的数据,最后一个批次特殊处理。eg:0-9,10-19…70-74

二、代码实现:

public static void main(String[] args) {
        List sourList = new ArrayList();
        //1、将list中放进去75个数据--模拟待处理数据集
        for (int i = 0; i < 75; i++) {
            sourList.add(i);
        }

        //假设每N个(10个)数据一处理(实际应用中可能每N个开一个线程,或者每N个调一次数据库操作)
        int batchSize = 10;
        //tempList存放批次数据
        List<Object> tempList = new ArrayList<Object>();

        //方法一:循环遍历待处理的处理(可能多线程,可能分批次入数据库,总之需要按每次处理的大小,计算需要处理的批次数)
        for (int i = 0; i < sourList.size(); i++) {
            tempList.add(sourList.get(i));
            //判断批次,够N个数开始处理一波
            if ((i + 1) % batchSize == 0 || (i + 1) == sourList.size()) {
                log.info("处理数据集:{}", tempList.toString());
                //todo 你的线程处理,或者数据库操作等
                tempList.clear();
            }
        }
        log.info("************* 方法一结束 *************");

        //方法二:根据总数计算总批次数,每次从待处理数据集中截取要处理的数据
        int count = sourList.size();
        int totalPages = (count % batchSize > 0) ? (count / batchSize + 1) : (count / batchSize);
        log.info("待处理数据总条数:{},总批次数:{}", count, totalPages);

        int startIndex = 0;
        int endIndex = 0;
        if (totalPages > 0) {
            //循环批次数,计算待处理数据下标
            for (int pageNum = 1; pageNum <= totalPages; pageNum++) {
                //每批次计算要处理的数据起始位置。终止位置
                List subList = new ArrayList();
                startIndex = ((pageNum - 1) * batchSize);
                if (pageNum == totalPages) {
                    //考虑最后一个批次特殊处理
                    subList = sourList.subList(startIndex, count);
                } else {
                    endIndex = startIndex + batchSize;
                    subList = sourList.subList(startIndex, endIndex);
                }
                //todo 你的线程处理,或者数据库操作等
                log.info("处理第{}批次:{}", pageNum, subList.toString());
            }
        }
        log.info("************* 方法二结束 *************");
    }

========================打印结果:

17:42:03.357  处理数据集:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
17:42:03.368  处理数据集:[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
17:42:03.368  处理数据集:[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
17:42:03.368  处理数据集:[30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
17:42:03.368  处理数据集:[40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
17:42:03.368  处理数据集:[50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
17:42:03.368  处理数据集:[60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
17:42:03.368  处理数据集:[70, 71, 72, 73, 74]
17:42:03.368  ************* 方法一结束 *************
17:42:03.368  待处理数据总条数:75,总批次数:8
17:42:03.369  处理第1批次:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
17:42:03.369  处理第2批次:[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
17:42:03.369  处理第3批次:[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
17:42:03.369  处理第4批次:[30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
17:42:03.369  处理第5批次:[40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
17:42:03.369  处理第6批次:[50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
17:42:03.369  处理第7批次:[60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
17:42:03.369  处理第8批次:[70, 71, 72, 73, 74]
17:42:03.369  ************* 方法二结束 *************

这样每一波分批待处理的数据集合,以及位置你都能get到了,也就可以进行你的业务逻辑操作了,入库或者开线程了。

三、分页查询下游批次处理场景

如果是分页查询下游或者其他接口循环处理场景:

//每次导出或者查询下游接口等分页页大小(10页一查)
public final static int  PERSIZE = 10;
//查询第一次数据(返回的分页信息page中包含总页数、总条数)
Page<OrderInfoDTO> firstBOList = orderRepository.queryPrescriptionByOrderId(param);
//处理第一页查询回来的数据
if (CollectionUtil.isNotEmpty(firstBOList)) {
    log.info("处理数据{}条 ", firstBOList.size());
}
//页数大于1继续查询
int totalPages=firstBOList.getTotalPage();
if (totalPages > 1) {
    for (int pageNum = 2; pageNum <= totalPages; pageNum++) {
        query.setPageSize(PERSIZE);
        query.setPageNo(pageNum);
        Page<OrderInfoDTO> list = orderRepository.queryPrescriptionByOrderId(param);
        //todo 处理结果集
        log.info("处理第{}批次数据{}条 ", pageNum, list.size());
    }
}

四、MyBatis的批量插入

当我们有很多数据需要通过数据库操作,例如很多数据的更新或者插入, 如果用for循环的方式,里面一条条数据去调数据库操作,我们知道那样开销是很大的,会频繁的创建链接,带来性能问题,为了减少连接,我们通常会批量操作。

批量插入代码样例,已mybatis的批量插入活动表为例:

1、活动表简单表结构:

CREATE TABLE `activity` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `activity_id` bigint(20) DEFAULT NULL COMMENT '活动id',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT DEFAULT CHARSET=utf8 COMMENT='活动表';

2、业务层组装数据:

ActivityServiceImpl.java:

private void saveRecords() {
        List<Activity> recordList =  new ArrayList<>();
            Activity activity = new Activity();
            activity.setUserId(userId);
            activity.setActivityId(activityId);
            recordList.add(activity);
            //此处根据业务需求具体组装待处理集合list
           activityDao.insertBatchActivity(recordList);
    }

ActivityDao.java:

  void insertBatchActivity(List<Activity> recordList);

activityDao.xml中写法:

activityDao.xml:

 <!-- 批量插入活动表-->
    <insert id="insertBatchActivity">
        INSERT INTO activity
        (
        activity_id, user_id, create_time
        )
        VALUES
        <foreach collection="list" item="item" separator=",">
            (
            #{item.activityId},
            #{item.userId},
            now()
            )
        </foreach>
    </insert>

特别注意:mysql默认接受sql的大小是1048576(1M),即第三种方式若数据量超过1M会报如下异常:(可通过调整MySQL安装目录下的my.ini文件中[mysqld]段的"max_allowed_packet = 1M")

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只IT攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值