利用多线程解决select...xx in(...)的性能问题

一、前言

 

      已经有好久没有更博了,上次也不知道是什么时候了,还是那句话,写博客就是为了记录,点点滴滴,点到为止!

      最近一直在忙着组内产品上线发布和线下测试的事情,还有一系列的产品代码封版的事情,总之忙碌的时间过的那是相当的快;

      自己也一直想抽点时间写写心得体会,当然是关于产品的;但无奈我都劝住了自己,因为还没有到合适的时间,我要确保自己在状态最佳的时间点,大脑能够完全空下来的时候,再做长篇大论;

      切入本篇正题的最后一句话:“做项目容易,做产品难啊,做核心产品更难啊!”

      放一张产品的其中一个功能截图, 激励下自己,相信往后的路,一切都会越来越好!    

      

 

 

 

 

二、场景

  

      回到标题,相信大家在做批量查询时,一定用到过in这个条件,可能你会说,我不用in,我用联合查询啊,本篇讨论的是如果你用in,所以,就不要纠结了;

      比如,select * from xxxTable where id in(1,2,3,4......)

      比如,select * from xxxTable where name in ('a','b','c','d'......)

      如果括号里面的值少的话,比如50个还好,100个也还好,但是如果是1000个,10000万个呢,事实上业务系统不会一下子查询这么多的,不有分页控制着呢吗。但是如果你在sql语句中用了in这个条件,一但你参数的个数没有控制好,外界传了一个特别size大的数值过来了,一种情况是,查询响应的结果会很耗时,因为查询的数据量大不说且in本身效率也不高啊,第二个就是in后面跟的参数太长,可能直接在sql编译这关就被卡死了,idea会无情的告诉你:

     Prepared or callable statement has more than 2000 parameter markers

     遇到这种情况怎么办? 推荐一种方法,分批查,一次查询很少量,利用多线程并发查最后合并结果返回,以牺牲cpu切换的时间来换取查询性能上的快感,想必有些人一听到多线程就头大,难道就没有其他办法了吗?有啊,你可以从数据表结构本身调整、或者sql语句优化上做文章,当然这两种比较灵活就不在本篇的讨论范围内了;

     多线的好处我就不用多说了吧,当然用多线程也是有风险的好吧,秉承着不浪费new太多线程消耗内存资源,所以,最好用线程池管理着线程,用完的放到池子中等待下次用,不够的池子在扩,好吧,下面上demo,演示一遍就OK了!

 

三、多线程创建的方式那么多,选哪个呢?    

 

     多线的创建方式有四种,一种继承Thread类,一种实现Runable接口,再一种就是实现Callable接口,最后一种是利用线程池启动多线程;

     注意,select查询是要有结果返回的,二是继承Thread类和实现Runable接口效果是一样的,都要复写run方法,推荐使用实现的方式,因为继承在Java中是单线条的,实现是可以多实现的;但是这两种方式满足不了带结果返回的,因为线程执行完了,我要怎么拿到执行后的result呢,别急,我们来看下Callable接口是怎么定义的:

 

 

     那我就用这种吧,再结合Excutor批量提交任务(第四种方式,创建多线程),最后实现大批量in的高效查询!

 

 

 

四、项目结构

 

 

 

     话不多说,直接看关键代码片段:

 

@GetMapping("/query")
    public ResponseEntity query(){

        // 模拟10000个id
        List<Long> ids = new ArrayList<>();
        for (long i = 0; i <10000 ; i++) {
            ids.add(i);
        }

        // 将ids按多少个进行拆分
        List<List<Long>> parts = Lists.partition(ids, 50);

        // 有多少份,就有多少次请求,有多少次请求就有多少个task
        List<Callable<List<GxTask>>> tasks = new ArrayList<>();
        for (List<Long> part : parts) {
            GxTaskQuery taskQuery = new GxTaskQuery(taskService);
            taskQuery.setIds(part);
            tasks.add(taskQuery);
        }

        // 每一个任务(线程)执行的结果都是一个future对象,这个对象的数据就是List<GxTask>
        List<Future<List<GxTask>>> futures = new ArrayList<>();
        for (Callable<List<GxTask>> task : tasks) {
            // 提交任务 == 注意这时候并没有触发线程执行,就绪状态
            futures.add(executor.submit(task));
        }

        // 最终要查询的任务集
        List<GxTask> allTasks = new ArrayList<>();

        // 遍历执行结果,准备正式执行(get)
        for (Future<List<GxTask>> future : futures) {
            try {
                // 查询任务超过3s的直接弃掉(cancel取消任务)
                List<GxTask> taskList =future.get(3000,TimeUnit.MILLISECONDS );
                if(taskList !=null && taskList.size() != 0){
                    allTasks.addAll(taskList);
                }
            }catch (InterruptedException | ExecutionException | TimeoutException e){
                future.cancel(true);
            }
        }
        return ResponseEntity.ok(allTasks);
    }

 

     需要注意的有三点:

 

(1)ids分段,比如10000个id,每50或100个分一批,这里利用的是google的guava

(2)注意excutor提交的任务返回结果是Feature类型

 

 

(3)只有调用feature.get(x)方法才算真正执行任务,且这个执行的结果是可以设置等待时间的,如果超过设置的时间任务还没有返回结果,那么这个task就会被取消(中断cancel),这是必然啊,不能一个查询拖了全局查询的后腿吧,当然这种情况是很少会发生的。

 

 

 

 

五、测试

 

     代码里没有真正去查数据库,但是模拟了一把in的查询:

 

 

     直接项目启动,postman走一波测试:

 

 

测试结果:

 

 

 

 

六、GitHub项目地址

 

地址:https://github.com/kobeyk/MultiTask

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值