有个同窗,说是系统中出现性能问题了,说是让我帮助诊断一下。原本是不想花这时间的,结果耐不住对方的死缠乱打,只要答应帮看看。 java
故事发生的背景是,在文件上传的时候,有时间会有人上传了文件,可是最后没有使用上传的文件,这样就会产生一些垃圾文件。 git
原来软件做者就想写一个后台定时任务程序,来清除这些垃圾文件? 算法
因为做者坚决的不让我发她的SQL语句(这个我也理解,这么丑陋的SQL),因此这里就不发源代码了,发伪代码。 架构
void deleteMissLinkFile{ List fileList=getFileList(); List deleteFileList=new ArrayList(); for(file:fileList){ int count1=execute(select count(*) from ...); int count2=execute(select count(*) from ...); int count3=execute(select count(*) from ...); int count4=execute(select count(*) from ...); int count5=execute(select count(*) from ...); if(count1==0&&count2==0&&count3==0&&count4==0&&count5==0){ deleteFileList.add(file); } } delete(deleteFileList); }固然,这里我已经给进行了必定的加工,使得看起一漂亮了许多,实际上,嗯嗯,实在是丑。
这个时候的性能状况是怎么样的呢?说是表里的数据只有500多条,可是执行时间要100多秒,可是实际上实际的应用场景都远不止这个数量级,并且随着数据的增长,性能会呈指数级降低。 框架
我说你去加10万条记录测试一下,保证你一夜算不出来。 异步
好吧,废话少说,接下来看看怎么优化这段程序。 性能
在开始以前,咱们能够假设有N个文件,有M个文件引用表,并且假设全部的文件引用表中的记录条数都同样。 测试
很显然,原来的实现方法中执行了:1次文件数查询+N*M次统计操做 优化
先用成本最低的方式来优化一把: spa
void deleteMissLinkFile{ List fileList=getFileList(); List deleteFileList=new ArrayList(); for(file:fileList){ int count1=execute(select count(*) from ...); if(count1>0)continue; int count2=execute(select count(*) from ...); if(count2>0)continue; int count3=execute(select count(*) from ...); if(count3>0)continue; int count4=execute(select count(*) from ...); if(count4>0)continue; int count5=execute(select count(*) from ...); if(count1>0)continue; deleteFileList.add(file); } delete(deleteFileList); }嗯嗯,经过上面的重构,性能立刻就能够提高一倍。难看是难看了一点,可是1倍也是不小的提高哦。
缘由,原来是要把全部的统计值都算出来,再进行判断,经过上面的重构,平均只要查一半就能够退出了,因此性能会有1倍的提高。
1次文件数查询+N*M/2次统计操做
偶当时提醒她说,你能够把内外换换,性能就会提高许多,结果死活听不懂,。
实际上逻辑是这样的,因为统计操做的执行效率是很是低的,而带主键的查询速度是很是快的,也就是把逻辑从:遍历全部的文件看看引用次数是多少,改变成从全部文件列表中删除全部已经引用的文件,其他就是要删除的垃圾文件。
void deleteMissLinkFile{ List fileList=getFileList(); List refList1=execute(select file from tb1…) for(ref:refList1){ fileList.remove(ref) } List refList2=execute(select file from tb2…) for(ref:refList2){ fileList.remove(ref) } …… delete(deleteFileList); }经过上面的优化,须要执行的SQL语句是:
1+m 条SQL语句,其它都是大量的内存数据比对,相对来讲,性能会高太多,经过必定的技巧进行一些优化,会有更大的提高。
这种方式,我毛估估比原始的方式,能够提升两个数量级以上。
为何提升了两个左右数量级仍是说比较笨的方法呢?
由于这种方法虽然比原始的方法有了显著的提高,可是仍是存在严重的设计问题的。
首先,当数据量比较小的时候(这里的小是指与互联网应用中的数据相比),作彻底遍历是没有问题的,可是当数据量比较大的时候,用一条SQL来遍历全部的数据,就是有很是大的问题的。这个时候就要引入一系列的复杂问题来解决,好比:把单机计算变成集群计算,把整个计算变成分段时间,无论怎么样,都是很是复杂的处理过程。
下面就要推出最快的、最省事的、效率最高的方法。
其实通常来讲,只要是算法都是有优化空间和余地的,所以通常来讲本人不多把话说满的。此次本人使用了“最”字,那就是用来代表将来已经没有优化的空间了,那什么样的算法才能没有优化的空间呢?答案就是:啥也不作。
固然了,实际上也不可能啥也不作,问题就在哪里,你不作怎么可能好呢?
实际上就是把任务进行必定的分解。经过把架构进行合理的分析与设计,把全部的文件上传、删除都作成公共的方法(或服务),在须要与文件打交道的地方,凡是与文件打交道的时候,作以下处理:
自次,当要清理垃圾的时候,就很是简单的了,只要:
select ... from ... where ref_times=0
而后进行相应的清理工做就好。
这个时候就优化了处理模式,而且把文件引用数据的维护分解到业务工做的过程中,能够极大幅度的提高清理垃圾的处理效率。固然有的人说了:若是这么作,会使得个人业务处理过程变慢,那怎么办?其实也没有关系了,你能够把这个变成异步消息的方式,通知文件引用处理去作这件事情就好了,这样就不会影响到你的业务处理效率了。
经过上面的分析,咱们对文件上传过程当中的垃圾清理过程进行优化,并分析了原来的问题之所在,及后面3种优化方式及其优缺点对比。
固然,实际上许多朋友也会有更好的办法来解决,欢迎你们参与讨论,并批评指正。
若是,你喜欢个人博文,请关注我,以便收到个人最新动态。
若是对个人开源框架感兴趣,能够从这里获取到最新的代码,也能够访问Tiny官网获取更多的消息,或到Tiny社区进行即时交流。