记录一次涉及mysql超大数据量(3亿)的业务优化过程

        首先介绍一下本次的业务需求。企业company表里大约有3亿家企业,现在需要对所有的企业做一次类型穿透,举个例子,企业A的类型是1,现在就需要去股东表里找这家企业的最大股东企业B,如果股东B企业的类型也是1,再去找B企业的最大股东C,这样一直找下去,直到找到股东的源头企业或者个人,判断源头类型,如果是2,那么A,B,C企业的类型都将变成2,并存入新的库。

        本次业务的难点不在于业务逻辑代码的编写,而在于更快的将这3亿数据处理完。由于只是需要存到库里,所以该业务是在本地跑的。那优化的点无疑是两个方面:mysql数据库操作的优化,多线程的使用(所有的表都加了索引)。接下来我将具体记录本次的优化过程,如有错误或更好的方法建议,欢迎您在评论区留言。

1,前期遇到的坑:配置双数据源,具体的代码就不展示了,网上有很多。主要的问题是第二个数据源没有生效,所有的sql操作都指向了第一个数据源,后面发现是由与@mapper 和@MapperScan注解的冲突导致的。

2,代码部分展示:我的前期主要思路是每次执行1亿,分三次执行完毕。这1亿家企业分给10个线程,每个线程执行1000万数据,单个线程每次查询1万家企业,然后对每一家企业进行穿透,再入库。测试了一下在单线程的情况下,10万条数据要半个小时,3亿多的数据执行完大约要没日没夜的跑60天。多线程的情况下1亿数据大约要一周跑完,虽然还是很慢,但好在可以接受了。

以下是代码部分:

       

@Override
    public void holder(long start) {
        try{
            List<COMPANY> companyList = companyMapper.selectAll(start, start + 10000);
            List<HolderPenetrate> insertList = new ArrayList<>();
            for (COMPANY item : companyList){
                List<String> cnameTemp = new ArrayList<>();
                List<HolderPenetrate> holderPenetrates = holderPenetrateMapper.selectByCid(item.getId());
                int degree = 0;
                //先查找结果表是否存在
                if (holderPenetrates != null && !holderPenetrates.isEmpty()){
                    continue;
                }
                //递归方法
                recursionSearch(item, cnameTemp, degree);
            }
            log.info("结果:" + start + "-" + (start + 10000) + "成功");
        }catch (Exception e) {
            e.printStackTrace();
            log.error("结果:" + start + "-" + (start + 10000) + "失败");
        }
    }

    /**
     * 判断企业类型
     * @param company
     * @return
     */

    private Boolean getCompanyType (COMPANY company) {
            *****
    }


    /**
     * 递归查找企业-股东信息,判断最上层类型,确定一系列企业类型
     * @param company
     * @param cnameTemp
     * @param degree
     * @return
     */
    private String recursionSearch(COMPANY company, List<String> cnameTemp, int degree) {
        1.最外层记录每一次递归的企业名称,防止企业互为最大股东成死循环
        2.如果企业类型为2,直接插入这条数据,结束
        3.如果企业类型为1,则查询该企业最大股东,并进入递归,在最后一次确定最终类型,插入数据,返回企业类型
        
    }
    /**
     * 10个线程,每个线程跑1000万条数据
     * @throws InterruptedException
     */
void contextLoadsThread() throws InterruptedException {
        long sttime = System.currentTimeMillis();
        long max = 100000000L;
        long start = 0L;
        int threadNum = 10;
        final CountDownLatch cdl = new CountDownLatch(threadNum);
        long size = (max-start)/threadNum;
        ThreadPoolExecutor executor = new ThreadPoolExecutor(threadNum, 25, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50000), threadFactory);
        for (int k = 0; k < threadNum; k++){
            final int fk = k;
            executor.execute(() -> {
                long perStart = fk * size + start;
                long perMax = (fk + 1) * size + start;
                try{
                    while (perStart < perMax){
                        mainService.holder(perStart);
                        perStart += 10000;
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }
                finally {
                    cdl.countDown();
                }
            });
        }
        cdl.await();
        long endtime = System.currentTimeMillis();
        long res = (endtime - sttime)/1000;
        System.out.println("用时: " + res);
    }

3. 执行第一个1亿发现的问题以及优化:

     (1)越到后面,执行的速度越慢,后面发现是查询1万家企业用了limit,当limit到9千万的时候,查询速度是非常慢的,所以放弃使用limit改为用where id > < 来代替,因为企业表id是自增id且有索引的,所以这样的操作在数据量很大的情况下也不会很慢。

     (2)每当确定一条数据类型后直接执行插入操作,当数据量很大的情况下效率很低,所以将确定的数据存到一个list里面,当大于1000条后,执行一次批量插入操作。

     (3)我的电脑是12核的,10个线程有点少,后面直接上20个线程,每个线程跑500万条数据。

4. 结果:跑完第一个1亿条数据消耗的用时出乎意料,仅仅只用了14个小时,3亿的数据不到两天就跑完了!这个结果比单线程的效率提升了30倍,这也是第一次直观的感受到多线程+mysql优化后的效率提升,当然我相信还有可以优化的点,如果您有什么好的建议或方法,欢迎您在评论区留言,我们一起讨论,谢谢!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值