首先介绍一下本次的业务需求。企业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优化后的效率提升,当然我相信还有可以优化的点,如果您有什么好的建议或方法,欢迎您在评论区留言,我们一起讨论,谢谢!