【性能优化】查询大量数据还慢?试试这个

172 阅读2分钟

背景

日常需求开发过程中,遇到需要查询大量数据的场景时,是怎么解决的?用单线程慢慢查?还是使用深分页 的方式?今天给大家介绍如果使用多线程来快速查询大量数据。

概括

本文将会讨论当Mysql表大数据量的情况,如何查询大量数据,并附上对应的伪代码

核心信息

利用多线程的特点,将查询sql一起异步执行,最后汇总结果。打个比方来说一共100米的路程,一个人(单线程)跑100米和10个人(多线程)一起每人跑10米,这两者最终都是跑100米,但是明显后者的效率要优于前者。

伪代码示例

1、先定义一个简单的线程池

@Component
public class XXXThreadPool {

    private final Tracer tracer;

    private final ExecutorService findXXXPool;

    public XXXThreadPool(Tracer tracer) {

        this.tracer = tracer;

        this.findXXXPool = initFindXXXPool();
    }

    public ExecutorService getFindXXXPool() {
        return findXXXPool;
    }

    // ============================== private method ==============================

    private ExecutorService initFindXXXPool() {

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5,
                10,
                20L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(Integer.MAX_VALUE));

        // 线程池的其他属性可根据需要,自行添加

        // @formatter:off
        return TracedExecutorServiceProxy.delegate(executor, tracer, false);
        // @formatter:on
    }

}

2、查询方法(伪代码)

public List<XXX> findXXX() {

    // 查询数据的总数,用于分页,本文不提供dao层方法
    final long XXXCount = dao.getXXXCount();

    if (XXXCount == 0L) {
        return new ArrayList<>();
    }
    // pager分页查询的封装类,需要自己定义
    final List<Pager<XXX>> pagers = initPagers(XXXCount);
    
    JStopWatch stopWatch = new JStopWatch();
    
    //CountDownLatch接收一个int型参数,表示要等待的工作线程的个数
    final CountDownLatch gate = new CountDownLatch(pagers.size());
    // threadPool是步骤一定义的线程池,需要执行注入
    final ExecutorService pool = threadPool.getFindXXXPool();

    List<Future<List<XXX>>> futures = new LinkedList<>();

    for (int i = 0; i < pagers.size(); i++) {
         //将分页查询任务加入到线程池,进行异步处理
        futures.add(pool.submit(new FindXXXPagerTask(i, gate, pagers.get(i))));
    }

    try {
        //主线程进入等待状态,等待其他线程执行完成
        gate.await();
    } catch (Exception e) {
        // do nothing
    }

    logger.info("[XXX] Concurrent find cost {} ms.", stopWatch.elapsed());
    //汇总数据
    return getXXX(futures);
}

3、线程池任务执行任务

private class FindXXXPagerTask implements Callable<List<XXX>> {

    private final int taskIndex;

    private final CountDownLatch gate;

    private final Pager<XXX> pager;

    public FindXXXPagerTask(int taskIndex, CountDownLatch gate, Pager<XXX> pager) {
        this.taskIndex = taskIndex;
        this.gate = gate;
        this.pager = pager;
    }

    @Override
    public List<XXX> call() {


        try {
            // 对应的分页查询SQL语句
            return dao.getPagedList(pager).getData();
        } finally {
            // 执行完成后,工作线程数减1
            gate.countDown();
        }

    }
}

4、其他方法

private List<Pager<XXX>> initPagers(long total) {

    int size = getSize4Pagers(total);
    List<Pager<XXX>> pagers = new ArrayList<>(size);

    for (int i = 0; i < size; i++) {
        pagers.add(buildPager(i));
    }

    return pagers;

}

private Pager<XXX> buildPager(int index) {

    // @formatter:off
    return new Pager<>()
            .setAutoCount(false)
            .setSkip(PAGE_LIMIT * index)
            .setLimit(PAGE_LIMIT)
            .addAscOrderBy("id");
}

private int getSize4Pagers(long total) {

    long count = total / PAGE_LIMIT;

    if (total % PAGE_LIMIT != 0) {
        count++;
    }

    return ConvertUtil.toIntValue(count);
}

private List<XXX> getXXX(List<Future<List<XXX>>> futures) {

    List<XXX> temp = null;
    List<XXX> result = new LinkedList<>();

    for (Future<List<XXX>> future : futures) {

        try {
            temp = future.get();
        } catch (Exception e) {
            temp = null;
        }

        if (temp != null) {
            result.addAll(temp);
        }
    }

    return result;
}

总结

当业务需要从表中查出大数据量时,而单线程的查询效率又无法满足时,可以采用多线程查询的方式,速度上根据设置的线程池核心线程数,效率有大幅度的提升,起码有一倍以上。