背景
日常需求开发过程中,遇到需要查询大量数据的场景时,是怎么解决的?用单线程慢慢查?还是使用深分页 的方式?今天给大家介绍如果使用多线程来快速查询大量数据。
概括
本文将会讨论当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;
}
总结
当业务需要从表中查出大数据量时,而单线程的查询效率又无法满足时,可以采用多线程查询的方式,速度上根据设置的线程池核心线程数,效率有大幅度的提升,起码有一倍以上。